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 ref inv = matrix[(row, row)].recip();
51 for i in row + 1..m {
52 matrix[(i, row)] *= inv;
53 }
54 let (_, A01, A10, A11) = matrix.rb_mut().split_at_mut(row + 1, row + 1);
55 let A01 = A01.row(row);
56 let A10 = A10.col(row);
57 linalg::matmul::matmul(
58 A11,
59 Accum::Add,
60 A10.as_mat(),
61 A01.as_mat(),
62 -one::<T>(),
63 Par::Seq,
64 );
65 }
66 n_trans
67}
68pub(crate) fn lu_in_place_recursion<I: Index, T: ComplexField>(
69 A: MatMut<'_, T>,
70 start: usize,
71 end: usize,
72 trans: &mut [I],
73 par: Par,
74 params: Spec<PartialPivLuParams, T>,
75) -> usize {
76 let params = params.config;
77 let mut A = A;
78 let m = A.nrows();
79 let ncols = A.ncols();
80 let n = end - start;
81 if n <= params.recursion_threshold {
82 return lu_in_place_unblocked(A, start, end, trans);
83 }
84 let half = n / 2;
85 let pow = Ord::min(16, half.next_power_of_two());
86 let block_size = half.next_multiple_of(pow);
87 let mut n_trans = 0;
88 assert!(n <= m);
89 n_trans += lu_in_place_recursion(
90 A.rb_mut().get_mut(.., start..end),
91 0,
92 block_size,
93 &mut trans[..block_size],
94 par,
95 params.into(),
96 );
97 {
98 let mut A = A.rb_mut().get_mut(.., start..end);
99 let (A00, mut A01, A10, mut A11) =
100 A.rb_mut().split_at_mut(block_size, block_size);
101 let A00 = A00.rb();
102 let A10 = A10.rb();
103 let work = A11
116 .nrows()
117 .saturating_mul(A11.ncols())
118 .saturating_mul(if T::IS_REAL { 2 } else { 8 });
119 let par_here = if work > params.par_threshold.saturating_mul(2) {
120 par
121 } else {
122 Par::Seq
123 };
124 {
125 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
126 A00.rb(),
127 A01.rb_mut(),
128 par_here,
129 );
130 }
131 linalg::matmul::matmul(
132 A11.rb_mut(),
133 Accum::Add,
134 A10.rb(),
135 A01.rb(),
136 -one::<T>(),
137 par_here,
138 );
139 n_trans += lu_in_place_recursion(
140 A.rb_mut().get_mut(block_size..m, ..),
141 block_size,
142 n,
143 &mut trans[block_size..n],
144 par,
145 params.into(),
146 );
147 }
148 let swap = |mat: MatMut<'_, T>| {
149 let mut mat = mat;
150 for j in 0..mat.ncols() {
151 let mut col = mat.rb_mut().col_mut(j);
152 if col.row_stride() == 1 {
153 for (j, &t) in trans[..n].iter().enumerate() {
154 swap_elems(col.rb_mut(), j, t.zx() + j);
155 }
156 } else {
157 for (j, &t) in trans[..n].iter().enumerate() {
158 swap_elems(col.rb_mut(), j, t.zx() + j);
159 }
160 }
161 }
162 };
163 let (A_left, A_right) = A.rb_mut().split_at_col_mut(start);
164 let A_right = A_right.get_mut(.., end - start..ncols - start);
165 let par = if m * (ncols - n) > params.par_threshold {
166 par
167 } else {
168 Par::Seq
169 };
170 match par {
171 Par::Seq => {
172 swap(A_left);
173 swap(A_right);
174 },
175 #[cfg(feature = "rayon")]
176 Par::Rayon(nthreads) => {
177 let nthreads = nthreads.get();
178 let len = (A_left.ncols() + A_right.ncols()) as f64;
179 let left_threads = Ord::min(
180 (nthreads as f64 * (A_left.ncols() as f64 / len)) as usize,
181 nthreads,
182 );
183 let right_threads = nthreads - left_threads;
184 crate::utils::thread::join_raw(
185 |_| {
186 if A_left.ncols() > 0 {
187 spindle::for_each(
188 left_threads,
189 A_left.par_col_partition_mut(left_threads),
190 |A| swap(A),
191 )
192 }
193 },
194 |_| {
195 if A_right.ncols() > 0 {
196 spindle::for_each(
197 right_threads,
198 A_right.par_col_partition_mut(right_threads),
199 |A| swap(A),
200 )
201 }
202 },
203 par,
204 );
205 },
206 }
207 n_trans
208}
209#[derive(Copy, Clone, Debug)]
211pub struct PartialPivLuParams {
212 pub recursion_threshold: usize,
214 pub block_size: usize,
216 pub par_threshold: usize,
220 #[doc(hidden)]
221 pub non_exhaustive: NonExhaustive,
222}
223#[derive(Copy, Clone, Debug)]
225pub struct PartialPivLuInfo {
226 pub transposition_count: usize,
229}
230#[derive(Copy, Clone, Debug)]
232pub enum LdltError {
233 ZeroPivot { index: usize },
234}
235impl<T: ComplexField> Auto<T> for PartialPivLuParams {
236 #[inline]
237 fn auto() -> Self {
238 Self {
239 recursion_threshold: 16,
240 block_size: 64,
241 par_threshold: 128 * 128,
242 non_exhaustive: NonExhaustive(()),
243 }
244 }
245}
246#[inline]
247pub fn lu_in_place_scratch<I: Index, T: ComplexField>(
248 nrows: usize,
249 ncols: usize,
250 par: Par,
251 params: Spec<PartialPivLuParams, T>,
252) -> StackReq {
253 _ = par;
254 _ = params;
255 StackReq::new::<I>(Ord::min(nrows, ncols))
256}
257pub fn lu_in_place<'out, I: Index, T: ComplexField>(
258 A: MatMut<'_, T>,
259 perm: &'out mut [I],
260 perm_inv: &'out mut [I],
261 par: Par,
262 stack: &mut MemStack,
263 params: Spec<PartialPivLuParams, T>,
264) -> (PartialPivLuInfo, PermRef<'out, I>) {
265 let _ = ¶ms;
266 let truncate = I::truncate;
267 #[cfg(feature = "perf-warn")]
268 if (A.col_stride().unsigned_abs() == 1
269 || A.row_stride().unsigned_abs() != 1)
270 && crate::__perf_warn!(LU_WARN)
271 {
272 log::warn!(
273 target : "faer_perf",
274 "LU with partial pivoting prefers column-major or row-major matrix. Found matrix with generic strides."
275 );
276 }
277 let mut matrix = A;
278 let mut stack = stack;
279 let m = matrix.nrows();
280 let n = matrix.ncols();
281 if matrix.row_stride().unsigned_abs() != 1
292 && matrix.nrows() >= matrix.ncols()
293 && matrix.ncols() > 1
294 && linalg::row_major_copy_pays(matrix.nrows(), matrix.ncols())
295 {
296 let mut work = alloc::vec![zero::<T>(); m * n];
297 let mut work = MatMut::from_column_major_slice_mut(&mut work, m, n);
298 linalg::copy_across_layouts(work.rb_mut(), matrix.rb(), par);
299 let ret = lu_in_place(
300 work.rb_mut(),
301 perm,
302 perm_inv,
303 par,
304 stack,
305 params,
306 );
307 linalg::copy_across_layouts(matrix, work.rb(), par);
308 return ret;
309 }
310 let size = Ord::min(n, m);
311 for i in 0..m {
312 let p = &mut perm[i];
313 *p = truncate(i);
314 }
315 let (mut transpositions, _) =
316 stack.rb_mut().make_with(size, |_| truncate(0));
317 let transpositions = transpositions.as_mut();
318 let n_transpositions = lu_in_place_recursion(
319 matrix.rb_mut(),
320 0,
321 size,
322 transpositions.as_mut(),
323 par,
324 params,
325 );
326 for idx in 0..size {
327 let t = transpositions[idx];
328 perm.as_mut().swap(idx, idx + t.zx());
329 }
330 if m < n {
331 let (left, right) = matrix.split_at_col_mut(size);
332 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
333 left.rb(),
334 right,
335 par,
336 );
337 }
338 for i in 0..m {
339 perm_inv[perm[i].zx()] = truncate(i);
340 }
341 (
342 PartialPivLuInfo {
343 transposition_count: n_transpositions,
344 },
345 unsafe { PermRef::new_unchecked(perm, perm_inv, m) },
346 )
347}
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::stats::prelude::*;
352 use crate::utils::approx::*;
353 use crate::{Mat, assert, c64};
354 use dyn_stack::MemBuffer;
355 #[test]
356 fn test_plu() {
357 let rng = &mut StdRng::seed_from_u64(0);
358 let approx_eq = CwiseMat(ApproxEq {
359 abs_tol: 1e-13,
360 rel_tol: 1e-13,
361 });
362 for n in [1, 2, 3, 128, 255, 256, 257] {
363 let A = CwiseMatDistribution {
364 nrows: n,
365 ncols: n,
366 dist: StandardNormal,
367 }
368 .rand::<Mat<f64>>(rng);
369 let A = A.as_ref();
370 let mut LU = A.cloned();
371 let perm = &mut *vec![0usize; n];
372 let perm_inv = &mut *vec![0usize; n];
373 let params = PartialPivLuParams {
374 recursion_threshold: 2,
375 block_size: 2,
376 ..auto!(f64)
377 };
378 let p = lu_in_place(
379 LU.as_mut(),
380 perm,
381 perm_inv,
382 Par::Seq,
383 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
384 usize,
385 f64,
386 >(
387 n,
388 n,
389 Par::Seq,
390 params.into(),
391 ))),
392 params.into(),
393 )
394 .1;
395 let mut L = LU.as_ref().cloned();
396 let mut U = LU.as_ref().cloned();
397 for j in 0..n {
398 for i in 0..j {
399 L[(i, j)] = 0.0;
400 }
401 L[(j, j)] = 1.0;
402 }
403 for j in 0..n {
404 for i in j + 1..n {
405 U[(i, j)] = 0.0;
406 }
407 }
408 let L = L.as_ref();
409 let U = U.as_ref();
410 assert!(p.inverse() * L * U ~ A);
411 }
412 for m in [8, 128, 255, 256, 257] {
413 let n = 8;
414 let A = CwiseMatDistribution {
415 nrows: m,
416 ncols: n,
417 dist: StandardNormal,
418 }
419 .rand::<Mat<f64>>(rng);
420 let A = A.as_ref();
421 let mut LU = A.cloned();
422 let perm = &mut *vec![0usize; m];
423 let perm_inv = &mut *vec![0usize; m];
424 let p = lu_in_place(
425 LU.as_mut(),
426 perm,
427 perm_inv,
428 Par::Seq,
429 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
430 usize,
431 f64,
432 >(
433 n, n, Par::Seq, default()
434 ))),
435 default(),
436 )
437 .1;
438 let mut L = LU.as_ref().cloned();
439 let mut U = LU.as_ref().cloned();
440 for j in 0..n {
441 for i in 0..j {
442 L[(i, j)] = 0.0;
443 }
444 L[(j, j)] = 1.0;
445 }
446 for j in 0..n {
447 for i in j + 1..m {
448 U[(i, j)] = 0.0;
449 }
450 }
451 let L = L.as_ref();
452 let U = U.as_ref();
453 let U = U.subrows(0, n);
454 assert!(p.inverse() * L * U ~ A);
455 }
456 }
457 fn factor_lu(A: &Mat<f64>, par: Par) -> (Mat<f64>, Vec<usize>, usize) {
458 let n = A.nrows();
459 let mut LU = A.cloned();
460 let perm = &mut *vec![0usize; n];
461 let perm_inv = &mut *vec![0usize; n];
462 let params: Spec<PartialPivLuParams, f64> = default();
463 let stack =
464 &mut MemBuffer::new(lu_in_place_scratch::<usize, f64>(n, n, par, params));
465 let stack = MemStack::new(stack);
466 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
467 (LU, perm.to_vec(), info.transposition_count)
468 }
469 fn factor_lu_c64(A: &Mat<c64>, par: Par) -> (Mat<c64>, Vec<usize>, usize) {
470 let n = A.nrows();
471 let mut LU = A.cloned();
472 let perm = &mut *vec![0usize; n];
473 let perm_inv = &mut *vec![0usize; n];
474 let params: Spec<PartialPivLuParams, c64> = default();
475 let stack =
476 &mut MemBuffer::new(lu_in_place_scratch::<usize, c64>(n, n, par, params));
477 let stack = MemStack::new(stack);
478 let (info, _) = lu_in_place(LU.as_mut(), perm, perm_inv, par, stack, params);
479 (LU, perm.to_vec(), info.transposition_count)
480 }
481 #[test]
489 fn threaded_lu_matches_the_sequential_one() {
490 let rng = &mut StdRng::seed_from_u64(0);
491 let pool = rayon::ThreadPoolBuilder::new()
492 .num_threads(4)
493 .build()
494 .unwrap();
495 let approx_eq = CwiseMat(ApproxEq {
496 abs_tol: 1e-12,
497 rel_tol: 1e-12,
498 });
499 for n in [96, 128, 192, 256, 512] {
500 let A = CwiseMatDistribution {
501 nrows: n,
502 ncols: n,
503 dist: StandardNormal,
504 }
505 .rand::<Mat<f64>>(rng);
506 let (seq, perm_seq, trans_seq) = factor_lu(&A, Par::Seq);
507 let (par, perm_par, trans_par) =
508 pool.install(|| factor_lu(&A, Par::rayon(4)));
509 assert!(seq.as_ref() ~ par.as_ref());
510 assert!(perm_seq == perm_par);
511 assert!(trans_seq == trans_par);
512 }
513 for n in [96, 128, 192, 256, 512] {
514 let real = CwiseMatDistribution {
515 nrows: n,
516 ncols: n,
517 dist: StandardNormal,
518 }
519 .rand::<Mat<f64>>(rng);
520 let imag = CwiseMatDistribution {
521 nrows: n,
522 ncols: n,
523 dist: StandardNormal,
524 }
525 .rand::<Mat<f64>>(rng);
526 let A = Mat::<c64>::from_fn(n, n, |i, j| c64::new(real[(i, j)], imag[(i, j)]));
527 let (seq, perm_seq, trans_seq) = factor_lu_c64(&A, Par::Seq);
528 let (par, perm_par, trans_par) =
529 pool.install(|| factor_lu_c64(&A, Par::rayon(4)));
530 for j in 0..n {
531 for i in 0..n {
532 let diff = (seq[(i, j)] - par[(i, j)]).abs();
533 assert!(diff <= 1e-10, "({i}, {j}) differs by {diff:e}");
534 }
535 }
536 assert!(perm_seq == perm_par);
537 assert!(trans_seq == trans_par);
538 }
539 }
540}