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}
86pub(crate) fn lu_in_place_recursion<I: Index, T: ComplexField>(
87 A: MatMut<'_, T>,
88 start: usize,
89 end: usize,
90 trans: &mut [I],
91 par: Par,
92 params: Spec<PartialPivLuParams, T>,
93) -> usize {
94 let params = params.config;
95 let mut A = A;
96 let m = A.nrows();
97 let ncols = A.ncols();
98 let n = end - start;
99 if n <= params.recursion_threshold {
100 return lu_in_place_unblocked(A, start, end, trans);
101 }
102 let half = n / 2;
103 let pow = Ord::min(16, half.next_power_of_two());
104 let block_size = half.next_multiple_of(pow);
105 let mut n_trans = 0;
106 assert!(n <= m);
107 n_trans += lu_in_place_recursion(
108 A.rb_mut().get_mut(.., start..end),
109 0,
110 block_size,
111 &mut trans[..block_size],
112 par,
113 params.into(),
114 );
115 {
116 let mut A = A.rb_mut().get_mut(.., start..end);
117 let (A00, mut A01, A10, mut A11) =
118 A.rb_mut().split_at_mut(block_size, block_size);
119 let A00 = A00.rb();
120 let A10 = A10.rb();
121 let work = A11
134 .nrows()
135 .saturating_mul(A11.ncols())
136 .saturating_mul(if T::IS_REAL { 2 } else { 8 });
137 let par_here = if work > params.par_threshold.saturating_mul(2) {
138 par
139 } else {
140 Par::Seq
141 };
142 {
143 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
144 A00.rb(),
145 A01.rb_mut(),
146 par_here,
147 );
148 }
149 linalg::matmul::matmul(
150 A11.rb_mut(),
151 Accum::Add,
152 A10.rb(),
153 A01.rb(),
154 -one::<T>(),
155 par_here,
156 );
157 n_trans += lu_in_place_recursion(
158 A.rb_mut().get_mut(block_size..m, ..),
159 block_size,
160 n,
161 &mut trans[block_size..n],
162 par,
163 params.into(),
164 );
165 }
166 let swap = |mat: MatMut<'_, T>| {
167 let mut mat = mat;
168 for j in 0..mat.ncols() {
169 let mut col = mat.rb_mut().col_mut(j);
170 if col.row_stride() == 1 {
171 for (j, &t) in trans[..n].iter().enumerate() {
172 swap_elems(col.rb_mut(), j, t.zx() + j);
173 }
174 } else {
175 for (j, &t) in trans[..n].iter().enumerate() {
176 swap_elems(col.rb_mut(), j, t.zx() + j);
177 }
178 }
179 }
180 };
181 let (A_left, A_right) = A.rb_mut().split_at_col_mut(start);
182 let A_right = A_right.get_mut(.., end - start..ncols - start);
183 let par = if m * (ncols - n) > params.par_threshold {
184 par
185 } else {
186 Par::Seq
187 };
188 match par {
189 Par::Seq => {
190 swap(A_left);
191 swap(A_right);
192 },
193 #[cfg(feature = "rayon")]
194 Par::Rayon(nthreads) => {
195 let nthreads = nthreads.get();
196 let len = (A_left.ncols() + A_right.ncols()) as f64;
197 let left_threads = Ord::min(
198 (nthreads as f64 * (A_left.ncols() as f64 / len)) as usize,
199 nthreads,
200 );
201 let right_threads = nthreads - left_threads;
202 crate::utils::thread::join_raw(
203 |_| {
204 if A_left.ncols() > 0 {
205 spindle::for_each(
206 left_threads,
207 A_left.par_col_partition_mut(left_threads),
208 |A| swap(A),
209 )
210 }
211 },
212 |_| {
213 if A_right.ncols() > 0 {
214 spindle::for_each(
215 right_threads,
216 A_right.par_col_partition_mut(right_threads),
217 |A| swap(A),
218 )
219 }
220 },
221 par,
222 );
223 },
224 }
225 n_trans
226}
227#[derive(Copy, Clone, Debug)]
229pub struct PartialPivLuParams {
230 pub recursion_threshold: usize,
232 pub block_size: usize,
234 pub par_threshold: usize,
238 #[doc(hidden)]
239 pub non_exhaustive: NonExhaustive,
240}
241#[derive(Copy, Clone, Debug)]
243pub struct PartialPivLuInfo {
244 pub transposition_count: usize,
247}
248#[derive(Copy, Clone, Debug)]
250pub enum LdltError {
251 ZeroPivot { index: usize },
252}
253impl<T: ComplexField> Auto<T> for PartialPivLuParams {
254 #[inline]
255 fn auto() -> Self {
256 Self {
257 recursion_threshold: 16,
258 block_size: 64,
259 par_threshold: 128 * 128,
260 non_exhaustive: NonExhaustive(()),
261 }
262 }
263}
264#[inline]
265pub fn lu_in_place_scratch<I: Index, T: ComplexField>(
266 nrows: usize,
267 ncols: usize,
268 par: Par,
269 params: Spec<PartialPivLuParams, T>,
270) -> StackReq {
271 _ = par;
272 _ = params;
273 StackReq::new::<I>(Ord::min(nrows, ncols))
274}
275pub fn lu_in_place<'out, I: Index, T: ComplexField>(
276 A: MatMut<'_, T>,
277 perm: &'out mut [I],
278 perm_inv: &'out mut [I],
279 par: Par,
280 stack: &mut MemStack,
281 params: Spec<PartialPivLuParams, T>,
282) -> (PartialPivLuInfo, PermRef<'out, I>) {
283 let _ = ¶ms;
284 let truncate = I::truncate;
285 #[cfg(feature = "perf-warn")]
286 if (A.col_stride().unsigned_abs() == 1
287 || A.row_stride().unsigned_abs() != 1)
288 && crate::__perf_warn!(LU_WARN)
289 {
290 log::warn!(
291 target : "faer_perf",
292 "LU with partial pivoting prefers column-major or row-major matrix. Found matrix with generic strides."
293 );
294 }
295 let mut matrix = A;
296 let mut stack = stack;
297 let m = matrix.nrows();
298 let n = matrix.ncols();
299 if matrix.row_stride().unsigned_abs() != 1
310 && matrix.nrows() >= matrix.ncols()
311 && matrix.ncols() > 1
312 && linalg::row_major_copy_pays(matrix.nrows(), matrix.ncols())
313 {
314 let mut work = alloc::vec![zero::<T>(); m * n];
315 let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
316 linalg::copy_across_layouts(work.rb_mut(), matrix.rb(), par);
317 let ret = lu_in_place(
318 work.rb_mut(),
319 perm,
320 perm_inv,
321 par,
322 stack,
323 params,
324 );
325 linalg::copy_across_layouts(matrix, work.rb(), par);
326 return ret;
327 }
328 let size = Ord::min(n, m);
329 for i in 0..m {
330 let p = &mut perm[i];
331 *p = truncate(i);
332 }
333 let (mut transpositions, _) =
334 stack.rb_mut().make_with(size, |_| truncate(0));
335 let transpositions = transpositions.as_mut();
336 let n_transpositions = lu_in_place_recursion(
337 matrix.rb_mut(),
338 0,
339 size,
340 transpositions.as_mut(),
341 par,
342 params,
343 );
344 for idx in 0..size {
345 let t = transpositions[idx];
346 perm.as_mut().swap(idx, idx + t.zx());
347 }
348 if m < n {
349 let (left, right) = matrix.split_at_col_mut(size);
350 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
351 left.rb(),
352 right,
353 par,
354 );
355 }
356 for i in 0..m {
357 perm_inv[perm[i].zx()] = truncate(i);
358 }
359 (
360 PartialPivLuInfo {
361 transposition_count: n_transpositions,
362 },
363 unsafe { PermRef::new_unchecked(perm, perm_inv, m) },
364 )
365}
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use crate::stats::prelude::*;
370 use crate::utils::approx::*;
371 use crate::{Mat, assert, c32, c64};
372 use dyn_stack::MemBuffer;
373 #[test]
374 fn test_plu() {
375 let rng = &mut StdRng::seed_from_u64(0);
376 let approx_eq = CwiseMat(ApproxEq {
377 abs_tol: 1e-13,
378 rel_tol: 1e-13,
379 });
380 for n in [1, 2, 3, 128, 255, 256, 257] {
381 let A = CwiseMatDistribution {
382 nrows: n,
383 ncols: n,
384 dist: StandardNormal,
385 }
386 .rand::<Mat<f64>>(rng);
387 let A = A.as_ref();
388 let mut LU = A.cloned();
389 let perm = &mut *vec![0usize; n];
390 let perm_inv = &mut *vec![0usize; n];
391 let params = PartialPivLuParams {
392 recursion_threshold: 2,
393 block_size: 2,
394 ..auto!(f64)
395 };
396 let p = lu_in_place(
397 LU.as_mut(),
398 perm,
399 perm_inv,
400 Par::Seq,
401 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
402 usize,
403 f64,
404 >(
405 n,
406 n,
407 Par::Seq,
408 params.into(),
409 ))),
410 params.into(),
411 )
412 .1;
413 let mut L = LU.as_ref().cloned();
414 let mut U = LU.as_ref().cloned();
415 for j in 0..n {
416 for i in 0..j {
417 L[(i, j)] = 0.0;
418 }
419 L[(j, j)] = 1.0;
420 }
421 for j in 0..n {
422 for i in j + 1..n {
423 U[(i, j)] = 0.0;
424 }
425 }
426 let L = L.as_ref();
427 let U = U.as_ref();
428 assert!(p.inverse() * L * U ~ A);
429 }
430 for m in [8, 128, 255, 256, 257] {
431 let n = 8;
432 let A = CwiseMatDistribution {
433 nrows: m,
434 ncols: n,
435 dist: StandardNormal,
436 }
437 .rand::<Mat<f64>>(rng);
438 let A = A.as_ref();
439 let mut LU = A.cloned();
440 let perm = &mut *vec![0usize; m];
441 let perm_inv = &mut *vec![0usize; m];
442 let p = lu_in_place(
443 LU.as_mut(),
444 perm,
445 perm_inv,
446 Par::Seq,
447 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
448 usize,
449 f64,
450 >(
451 n, n, Par::Seq, default()
452 ))),
453 default(),
454 )
455 .1;
456 let mut L = LU.as_ref().cloned();
457 let mut U = LU.as_ref().cloned();
458 for j in 0..n {
459 for i in 0..j {
460 L[(i, j)] = 0.0;
461 }
462 L[(j, j)] = 1.0;
463 }
464 for j in 0..n {
465 for i in j + 1..m {
466 U[(i, j)] = 0.0;
467 }
468 }
469 let L = L.as_ref();
470 let U = U.as_ref();
471 let U = U.subrows(0, n);
472 assert!(p.inverse() * L * U ~ A);
473 }
474 }
475 fn factor_lu(A: &Mat<f64>, par: Par) -> (Mat<f64>, Vec<usize>, usize) {
476 let n = A.nrows();
477 let mut LU = A.cloned();
478 let perm = &mut *vec![0usize; n];
479 let perm_inv = &mut *vec![0usize; n];
480 let params: Spec<PartialPivLuParams, f64> = default();
481 let stack =
482 &mut MemBuffer::new(lu_in_place_scratch::<usize, f64>(n, n, par, params));
483 let stack = MemStack::new(stack);
484 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
485 (LU, perm.to_vec(), info.transposition_count)
486 }
487 fn factor_lu_c64(A: &Mat<c64>, par: Par) -> (Mat<c64>, Vec<usize>, usize) {
488 let n = A.nrows();
489 let mut LU = A.cloned();
490 let perm = &mut *vec![0usize; n];
491 let perm_inv = &mut *vec![0usize; n];
492 let params: Spec<PartialPivLuParams, c64> = default();
493 let stack =
494 &mut MemBuffer::new(lu_in_place_scratch::<usize, c64>(n, n, par, params));
495 let stack = MemStack::new(stack);
496 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
497 (LU, perm.to_vec(), info.transposition_count)
498 }
499 #[test]
507 fn threaded_lu_matches_the_sequential_one() {
508 let rng = &mut StdRng::seed_from_u64(0);
509 let pool = rayon::ThreadPoolBuilder::new()
510 .num_threads(4)
511 .build()
512 .unwrap();
513 let approx_eq = CwiseMat(ApproxEq {
514 abs_tol: 1e-12,
515 rel_tol: 1e-12,
516 });
517 for n in [96, 128, 192, 256, 512] {
518 let A = CwiseMatDistribution {
519 nrows: n,
520 ncols: n,
521 dist: StandardNormal,
522 }
523 .rand::<Mat<f64>>(rng);
524 let (seq, perm_seq, trans_seq) = factor_lu(&A, Par::Seq);
525 let (par, perm_par, trans_par) =
526 pool.install(|| factor_lu(&A, Par::rayon(4)));
527 assert!(seq.as_ref() ~ par.as_ref());
528 assert!(perm_seq == perm_par);
529 assert!(trans_seq == trans_par);
530 }
531 for n in [96, 128, 192, 256, 512] {
532 let real = CwiseMatDistribution {
533 nrows: n,
534 ncols: n,
535 dist: StandardNormal,
536 }
537 .rand::<Mat<f64>>(rng);
538 let imag = CwiseMatDistribution {
539 nrows: n,
540 ncols: n,
541 dist: StandardNormal,
542 }
543 .rand::<Mat<f64>>(rng);
544 let A = Mat::<c64>::from_fn(n, n, |i, j| c64::new(real[(i, j)], imag[(i, j)]));
545 let (seq, perm_seq, trans_seq) = factor_lu_c64(&A, Par::Seq);
546 let (par, perm_par, trans_par) =
547 pool.install(|| factor_lu_c64(&A, Par::rayon(4)));
548 for j in 0..n {
549 for i in 0..n {
550 let diff = (seq[(i, j)] - par[(i, j)]).abs();
551 assert!(diff <= 1e-10, "({i}, {j}) differs by {diff:e}");
552 }
553 }
554 assert!(perm_seq == perm_par);
555 assert!(trans_seq == trans_par);
556 }
557 }
558 #[test]
564 fn zero_pivot_column_reconstructs() {
565 fn zero_pivot<T: ComplexField>(n: usize) -> Mat<T> {
566 Mat::<T>::from_fn(n, n, |i, j| {
567 if j > 0 {
568 from_f64::<T>((i + 2 * j) as f64)
569 } else {
570 zero()
571 }
572 })
573 }
574 fn subnormal<T: ComplexField>(tiny: f64) -> Mat<T> {
577 let tiny = from_f64::<T>(tiny);
578 Mat::<T>::from_fn(2, 2, |i, j| match (i, j) {
579 (0, 0) => tiny.copy(),
580 (0, 1) => one(),
581 (1, 0) => tiny.mul_real(from_f64::<T::Real>(0.5)),
582 (1, 1) => from_f64::<T>(2.0),
583 _ => zero(),
584 })
585 }
586 for n in [1usize, 2, 3, 128] {
587 check_factor::<f32>(zero_pivot::<f32>(n).as_ref(), None);
588 check_factor::<c32>(zero_pivot::<c32>(n).as_ref(), None);
589 check_factor::<f64>(zero_pivot::<f64>(n).as_ref(), None);
590 check_factor::<c64>(zero_pivot::<c64>(n).as_ref(), None);
591 }
592 check_factor::<f32>(
595 subnormal::<f32>(2f64.powi(-147)).as_ref(),
596 Some(0.5f32),
597 );
598 check_factor::<c32>(
599 subnormal::<c32>(2f64.powi(-147)).as_ref(),
600 Some(c32::new(0.5, 0.0)),
601 );
602 check_factor::<f64>(
603 subnormal::<f64>(2f64.powi(-1072)).as_ref(),
604 Some(0.5f64),
605 );
606 check_factor::<c64>(
607 subnormal::<c64>(2f64.powi(-1072)).as_ref(),
608 Some(c64::new(0.5, 0.0)),
609 );
610 }
611 fn check_factor<T: ComplexField>(a: MatRef<'_, T>, l10: Option<T>) {
612 let tol = eps::<T::Real>() * from_f64::<T::Real>(64.0);
613 let approx_eq = CwiseMat(ApproxEq {
614 abs_tol: tol.copy(),
615 rel_tol: tol,
616 });
617 let n = a.nrows();
618 for par in [Par::Seq, Par::rayon(8)] {
619 let mut LU = a.cloned();
620 let perm = &mut *vec![0usize; n];
621 let perm_inv = &mut *vec![0usize; n];
622 let params = PartialPivLuParams {
623 recursion_threshold: 2,
624 block_size: 2,
625 ..auto!(T)
626 };
627 let p = lu_in_place(
628 LU.as_mut(),
629 perm,
630 perm_inv,
631 par,
632 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
633 usize,
634 T,
635 >(
636 n, n, par, params.into()
637 ))),
638 params.into(),
639 )
640 .1;
641 assert!(LU.as_ref().is_all_finite());
642 let mut L = LU.as_ref().cloned();
643 let mut U = LU.as_ref().cloned();
644 for j in 0..n {
645 for i in 0..j {
646 L[(i, j)] = zero();
647 }
648 L[(j, j)] = one();
649 }
650 for j in 0..n {
651 for i in j + 1..n {
652 U[(i, j)] = zero();
653 }
654 }
655 assert!(p.inverse() * L.as_ref() * U.as_ref() ~ a);
656 if let Some(ref x) = l10 {
657 assert!(
658 (L[(1, 0)].copy() - x.copy()).abs()
659 <= eps::<T::Real>() * from_f64::<T::Real>(4.0)
660 );
661 }
662 }
663 }
664}