1use dyn_stack::{PodStack, SizeOverflow, StackReq};
2use faer_core::{
3 assert, debug_assert,
4 group_helpers::*,
5 mul::matmul,
6 permutation::{Index, PermutationMut, SignedIndex},
7 solve::solve_unit_lower_triangular_in_place,
8 unzipped, zipped, ComplexField, Entity, MatMut, Parallelism, SimdCtx,
9};
10use faer_entity::*;
11use reborrow::*;
12
13#[inline(always)]
14fn swap_two_elems<E: ComplexField>(mut m: MatMut<'_, E>, i: usize, j: usize) {
15 debug_assert!(m.ncols() == 1);
16 debug_assert!(i < m.nrows());
17 debug_assert!(j < m.nrows());
18 unsafe {
19 let a = m.read_unchecked(i, 0);
20 let b = m.read_unchecked(j, 0);
21 m.write_unchecked(i, 0, b);
22 m.write_unchecked(j, 0, a);
23 }
24}
25
26#[inline(always)]
27fn swap_two_elems_contiguous<E: ComplexField>(m: MatMut<'_, E>, i: usize, j: usize) {
28 debug_assert!(m.ncols() == 1);
29 debug_assert!(m.row_stride() == 1);
30 debug_assert!(i < m.nrows());
31 debug_assert!(j < m.nrows());
32 unsafe {
33 let ptr = m.as_ptr_mut();
34 let ptr_a = E::faer_map(
35 E::faer_copy(&ptr),
36 #[inline(always)]
37 |ptr| ptr.add(i),
38 );
39 let ptr_b = E::faer_map(
40 E::faer_copy(&ptr),
41 #[inline(always)]
42 |ptr| ptr.add(j),
43 );
44
45 E::faer_map(
46 E::faer_zip(ptr_a, ptr_b),
47 #[inline(always)]
48 |(a, b)| core::ptr::swap(a, b),
49 );
50 }
51}
52
53#[inline(never)]
54fn lu_in_place_unblocked<E: ComplexField, I: Index>(
55 mut matrix: MatMut<'_, E>,
56 col_start: usize,
57 n: usize,
58 transpositions: &mut [I],
59) -> usize {
60 let m = matrix.nrows();
61 let ncols = matrix.ncols();
62 assert!(m >= n);
63
64 let truncate = <I::Signed as SignedIndex>::truncate;
65
66 if n == 0 {
67 return 0;
68 }
69
70 let mut n_transpositions = 0;
71
72 let arch = E::Simd::default();
73
74 for (k, t) in transpositions.iter_mut().enumerate() {
75 let mut imax = 0;
76 {
77 let col = k + col_start;
78 let col = matrix.rb().col(col).subrows(k, m - k);
79 let m = col.nrows();
80
81 let mut max = E::Real::faer_zero();
82
83 for i in 0..m {
84 let abs = unsafe { col.read_unchecked(i) }.faer_score();
85 if abs > max {
86 imax = i;
87 max = abs;
88 }
89 }
90
91 imax += k;
92 }
93
94 *t = I::from_signed(truncate(imax - k));
95
96 if imax != k {
97 n_transpositions += 1;
98 }
99
100 if k != imax {
101 for j in 0..ncols {
102 unsafe {
103 let mk = matrix.read_unchecked(k, j);
104 let mi = matrix.read_unchecked(imax, j);
105 matrix.write_unchecked(k, j, mi);
106 matrix.write_unchecked(imax, j, mk);
107 }
108 }
109 }
110
111 let (_, _, _, middle_right) = matrix.rb_mut().split_at_mut(0, col_start);
112 let (_, _, middle, _) = middle_right.split_at_mut(0, n);
113 update(arch, middle, k);
114 }
115
116 n_transpositions
117}
118
119struct Update<'a, E: ComplexField> {
120 matrix: MatMut<'a, E>,
121 j: usize,
122}
123
124impl<E: ComplexField> pulp::WithSimd for Update<'_, E> {
125 type Output = ();
126
127 #[inline(always)]
128 fn with_simd<S: pulp::Simd>(self, simd: S) -> Self::Output {
129 let Self { mut matrix, j } = self;
130
131 debug_assert_eq!(matrix.row_stride(), 1);
132
133 let m = matrix.nrows();
134 let inv = matrix.read(j, j).faer_inv();
135 for i in j + 1..m {
136 unsafe {
137 matrix.write_unchecked(i, j, matrix.read_unchecked(i, j).faer_mul(inv));
138 }
139 }
140 let (_, top_right, bottom_left, bottom_right) = matrix.rb_mut().split_at_mut(j + 1, j + 1);
141 let lhs = bottom_left.rb().col(j);
142 let rhs = top_right.rb().row(j);
143 let mut mat = bottom_right;
144
145 let lhs = SliceGroup::<'_, E>::new(lhs.try_get_contiguous_col());
146
147 let simd = SimdFor::<E, S>::new(simd);
148 let offset = simd.align_offset(lhs);
149 let (lhs_head, lhs_body, lhs_tail) = simd.as_aligned_simd(lhs, offset);
150
151 for k in 0..mat.ncols() {
152 let acc = SliceGroupMut::<'_, E>::new(mat.rb_mut().try_get_contiguous_col_mut(k));
153 let rhs = simd.splat(rhs.read(k).faer_neg());
154 let (acc_head, acc_body, acc_tail) = simd.as_aligned_simd_mut(acc, offset);
155
156 #[inline(always)]
157 fn process<E: ComplexField, S: pulp::Simd>(
158 simd: SimdFor<E, S>,
159 mut acc: impl Write<Output = SimdGroupFor<E, S>>,
160 lhs: impl Read<Output = SimdGroupFor<E, S>>,
161 rhs: SimdGroupFor<E, S>,
162 ) {
163 let zero = simd.splat(E::faer_zero());
164 acc.write(simd.mul_add_e(rhs, lhs.read_or(zero), acc.read_or(zero)));
165 }
166
167 process(simd, acc_head, lhs_head, rhs);
168 for (acc, lhs) in acc_body.into_mut_iter().zip(lhs_body.into_ref_iter()) {
169 process(simd, acc, lhs, rhs);
170 }
171 process(simd, acc_tail, lhs_tail, rhs);
172 }
173 }
174}
175
176fn update<E: ComplexField>(arch: E::Simd, mut matrix: MatMut<E>, j: usize) {
177 if matrix.row_stride() == 1 {
178 arch.dispatch(Update { matrix, j });
179 } else {
180 let m = matrix.nrows();
181 let inv = matrix.read(j, j).faer_inv();
182 for i in j + 1..m {
183 matrix.write(i, j, matrix.read(i, j).faer_mul(inv));
184 }
185 let (_, top_right, bottom_left, bottom_right) = matrix.rb_mut().split_at_mut(j + 1, j + 1);
186 let lhs = bottom_left.rb().col(j);
187 let rhs = top_right.rb().row(j);
188 let mut mat = bottom_right;
189
190 for k in 0..mat.ncols() {
191 let col = mat.rb_mut().col_mut(k);
192 let rhs = rhs.read(k);
193 zipped!(col.as_2d_mut(), lhs.as_2d()).for_each(|unzipped!(mut x, lhs)| {
194 x.write(x.read().faer_sub(lhs.read().faer_mul(rhs)))
195 });
196 }
197 }
198}
199
200#[allow(clippy::extra_unused_type_parameters)]
201fn recursion_threshold<E: Entity>(_m: usize) -> usize {
202 16
203}
204
205#[inline]
206#[allow(clippy::extra_unused_type_parameters)]
207fn blocksize<E: Entity>(n: usize) -> usize {
209 let base_rem = n / 2;
210 n - if n >= 32 {
211 (base_rem + 15) / 16 * 16
212 } else if n >= 16 {
213 (base_rem + 7) / 8 * 8
214 } else if n >= 8 {
215 (base_rem + 3) / 4 * 4
216 } else {
217 base_rem
218 }
219}
220
221#[doc(hidden)]
222pub fn lu_in_place_impl<I: Index, E: ComplexField>(
223 mut matrix: MatMut<'_, E>,
224 col_start: usize,
225 n: usize,
226 transpositions: &mut [I],
227 parallelism: Parallelism,
228) -> usize {
229 let m = matrix.nrows();
230 let full_n = matrix.ncols();
231
232 debug_assert!(m >= n);
233
234 if n <= recursion_threshold::<E>(m) {
235 return lu_in_place_unblocked(matrix, col_start, n, transpositions);
236 }
237
238 let bs = blocksize::<E>(n);
240
241 let mut n_transpositions = 0;
242
243 n_transpositions += lu_in_place_impl(
244 matrix.rb_mut().submatrix_mut(0, col_start, m, n),
245 0,
246 bs,
247 &mut transpositions[..bs],
248 parallelism,
249 );
250
251 let (mat_top_left, mut mat_top_right, mat_bot_left, mut mat_bot_right) = matrix
252 .rb_mut()
253 .submatrix_mut(0, col_start, m, n)
254 .split_at_mut(bs, bs);
255
256 solve_unit_lower_triangular_in_place(mat_top_left.rb(), mat_top_right.rb_mut(), parallelism);
257 matmul(
258 mat_bot_right.rb_mut(),
259 mat_bot_left.rb(),
260 mat_top_right.rb(),
261 Some(E::faer_one()),
262 E::faer_one().faer_neg(),
263 parallelism,
264 );
265
266 n_transpositions += lu_in_place_impl(
267 matrix.rb_mut().submatrix_mut(bs, col_start, m - bs, n),
268 bs,
269 n - bs,
270 &mut transpositions[bs..],
271 parallelism,
272 );
273
274 let parallelism = if m * (full_n - n) > 128 * 128 {
275 parallelism
276 } else {
277 Parallelism::None
278 };
279
280 if matrix.row_stride() == 1 {
281 faer_core::for_each_raw(
282 col_start + (full_n - (col_start + n)),
283 |j| {
284 let j = if j >= col_start { col_start + n + j } else { j };
285 let mut col = unsafe { matrix.rb().col(j).const_cast() };
286 for (i, &t) in transpositions[..bs].iter().enumerate() {
287 swap_two_elems_contiguous(
288 col.rb_mut().as_2d_mut(),
289 i,
290 t.to_signed().zx() + i.to_signed().zx(),
291 );
292 }
293 let (_, mut col) = col.split_at_mut(bs);
294 for (i, &t) in transpositions[bs..].iter().enumerate() {
295 swap_two_elems_contiguous(
296 col.rb_mut().as_2d_mut(),
297 i,
298 t.to_signed().zx() + i.to_signed().zx(),
299 );
300 }
301 },
302 parallelism,
303 );
304 } else {
305 faer_core::for_each_raw(
306 col_start + (full_n - (col_start + n)),
307 |j| {
308 let j = if j >= col_start { col_start + n + j } else { j };
309 let mut col = unsafe { matrix.rb().col(j).const_cast() };
310 for (i, &t) in transpositions[..bs].iter().enumerate() {
311 swap_two_elems(
312 col.rb_mut().as_2d_mut(),
313 i,
314 t.to_signed().zx() + i.to_signed().zx(),
315 );
316 }
317 let (_, mut col) = col.split_at_mut(bs);
318 for (i, &t) in transpositions[bs..].iter().enumerate() {
319 swap_two_elems(
320 col.rb_mut().as_2d_mut(),
321 i,
322 t.to_signed().zx() + i.to_signed().zx(),
323 );
324 }
325 },
326 parallelism,
327 );
328 }
329
330 n_transpositions
331}
332
333#[derive(Default, Copy, Clone)]
334#[non_exhaustive]
335pub struct PartialPivLuComputeParams {}
336
337#[derive(Copy, Clone, Debug)]
338pub struct PartialPivLuInfo {
339 pub transposition_count: usize,
340}
341
342pub fn lu_in_place_req<I: Index, E: Entity>(
345 m: usize,
346 n: usize,
347 parallelism: Parallelism,
348 params: PartialPivLuComputeParams,
349) -> Result<StackReq, SizeOverflow> {
350 let _ = ¶ms;
351 let _ = ¶llelism;
352
353 let size = Ord::min(n, m);
354 StackReq::try_new::<I>(size)
355}
356
357pub fn lu_in_place<'out, I: Index, E: ComplexField>(
384 matrix: MatMut<'_, E>,
385 perm: &'out mut [I],
386 perm_inv: &'out mut [I],
387 parallelism: Parallelism,
388 stack: PodStack<'_>,
389 params: PartialPivLuComputeParams,
390) -> (PartialPivLuInfo, PermutationMut<'out, I, E>) {
391 let _ = ¶ms;
392 let truncate = <I::Signed as SignedIndex>::truncate;
393
394 assert!(perm.len() == matrix.nrows());
395 assert!(perm_inv.len() == matrix.nrows());
396
397 #[cfg(feature = "perf-warn")]
398 if (matrix.col_stride().unsigned_abs() == 1 || matrix.row_stride().unsigned_abs() != 1)
399 && faer_core::__perf_warn!(LU_WARN)
400 {
401 log::warn!(target: "faer_perf", "LU with partial pivoting prefers column-major or row-major matrix. Found matrix with generic strides.");
402 }
403
404 let mut matrix = matrix;
405 let mut stack = stack;
406 let m = matrix.nrows();
407 let n = matrix.ncols();
408 let size = Ord::min(n, m);
409
410 for (i, p) in perm.iter_mut().enumerate() {
411 *p = I::from_signed(truncate(i));
412 }
413
414 let (transpositions, _) = stack
415 .rb_mut()
416 .make_with(size, |_| I::from_signed(truncate(0)));
417 let n_transpositions = lu_in_place_impl(matrix.rb_mut(), 0, size, transpositions, parallelism);
418
419 for (idx, t) in transpositions.iter().enumerate() {
420 perm.swap(idx, idx + t.to_signed().zx());
421 }
422
423 let (_, _, left, right) = matrix.split_at_mut(0, size);
424
425 if m < n {
426 solve_unit_lower_triangular_in_place(left.rb(), right, parallelism);
427 }
428
429 for (i, &p) in perm.iter().enumerate() {
430 perm_inv[p.to_signed().zx()] = I::from_signed(truncate(i));
431 }
432
433 (
434 PartialPivLuInfo {
435 transposition_count: n_transpositions,
436 },
437 unsafe { PermutationMut::new_unchecked(perm, perm_inv) },
438 )
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use crate::partial_pivoting::reconstruct;
445 use assert_approx_eq::assert_approx_eq;
446 use dyn_stack::GlobalPodBuffer;
447 use faer_core::{assert, permutation::PermutationRef, Mat, MatRef};
448 use rand::random;
449
450 macro_rules! make_stack {
451 ($req: expr) => {
452 ::dyn_stack::PodStack::new(&mut ::dyn_stack::GlobalPodBuffer::new($req.unwrap()))
453 };
454 }
455
456 fn reconstruct_matrix<I: Index, E: ComplexField>(
457 lu_factors: MatRef<'_, E>,
458 row_perm: PermutationRef<'_, I, E>,
459 ) -> Mat<E> {
460 let m = lu_factors.nrows();
461 let n = lu_factors.ncols();
462 let mut dst = Mat::zeros(m, n);
463 reconstruct::reconstruct(
464 dst.as_mut(),
465 lu_factors,
466 row_perm,
467 Parallelism::Rayon(0),
468 make_stack!(reconstruct::reconstruct_req::<I, E>(
469 m,
470 n,
471 Parallelism::Rayon(0)
472 )),
473 );
474 dst
475 }
476
477 #[test]
478 fn compute_lu() {
479 for (m, n) in [
480 (10, 10),
481 (4, 4),
482 (2, 4),
483 (2, 20),
484 (2, 2),
485 (20, 20),
486 (4, 2),
487 (20, 2),
488 (40, 20),
489 (20, 40),
490 (40, 60),
491 (60, 40),
492 (200, 100),
493 (100, 200),
494 (200, 200),
495 ] {
496 let mut mat = Mat::from_fn(m, n, |_, _| random::<f64>());
497 let mat_orig = mat.clone();
498 let mut perm = vec![0usize; m];
499 let mut perm_inv = vec![0; m];
500
501 let mut mem = GlobalPodBuffer::new(
502 lu_in_place_req::<usize, f64>(m, n, Parallelism::Rayon(8), Default::default())
503 .unwrap(),
504 );
505 let mut stack = PodStack::new(&mut mem);
506
507 let (_, row_perm) = lu_in_place(
508 mat.as_mut(),
509 &mut perm,
510 &mut perm_inv,
511 Parallelism::Rayon(8),
512 stack.rb_mut(),
513 Default::default(),
514 );
515 let reconstructed = reconstruct_matrix(mat.as_ref(), row_perm.rb());
516
517 for i in 0..m {
518 for j in 0..n {
519 assert_approx_eq!(mat_orig.read(i, j), reconstructed.read(i, j));
520 }
521 }
522 }
523 }
524
525 #[test]
526 fn compute_lu_non_contiguous() {
527 for (m, n) in [
528 (10, 10),
529 (4, 4),
530 (2, 4),
531 (2, 20),
532 (2, 2),
533 (20, 20),
534 (4, 2),
535 (20, 2),
536 (40, 20),
537 (20, 40),
538 (40, 60),
539 (60, 40),
540 (200, 100),
541 (100, 200),
542 (200, 200),
543 ] {
544 let mut mat = Mat::from_fn(m, n, |_, _| random::<f64>());
545 let mut mat = mat.as_mut().reverse_rows_mut();
546 let mat_orig = mat.to_owned();
547 let mut perm = vec![0usize; m];
548 let mut perm_inv = vec![0; m];
549
550 let mut mem = GlobalPodBuffer::new(
551 lu_in_place_req::<usize, f64>(m, n, Parallelism::Rayon(8), Default::default())
552 .unwrap(),
553 );
554 let mut stack = PodStack::new(&mut mem);
555
556 let (_, row_perm) = lu_in_place(
557 mat.rb_mut(),
558 &mut perm,
559 &mut perm_inv,
560 Parallelism::Rayon(8),
561 stack.rb_mut(),
562 Default::default(),
563 );
564 let reconstructed = reconstruct_matrix(mat.rb(), row_perm.rb());
565
566 for i in 0..m {
567 for j in 0..n {
568 assert_approx_eq!(mat_orig.read(i, j), reconstructed.read(i, j));
569 }
570 }
571 }
572 }
573
574 #[test]
575 fn compute_lu_row_major() {
576 for (m, n) in [
577 (3, 3),
578 (2, 2),
579 (4, 2),
580 (2, 4),
581 (4, 4),
582 (10, 10),
583 (2, 20),
584 (20, 20),
585 (20, 2),
586 (40, 20),
587 (20, 40),
588 (40, 60),
589 (60, 40),
590 (200, 100),
591 (100, 200),
592 (200, 200),
593 ] {
594 let mut mat = Mat::from_fn(n, m, |_, _| random::<f64>());
595 let mut mat = mat.as_mut().transpose_mut();
596 let mat_orig = mat.to_owned();
597 let mut perm = vec![0usize; m];
598 let mut perm_inv = vec![0; m];
599
600 let mut mem = GlobalPodBuffer::new(
601 lu_in_place_req::<usize, f64>(m, n, Parallelism::Rayon(8), Default::default())
602 .unwrap(),
603 );
604 let mut stack = PodStack::new(&mut mem);
605
606 let (_, row_perm) = lu_in_place(
607 mat.rb_mut(),
608 &mut perm,
609 &mut perm_inv,
610 Parallelism::Rayon(8),
611 stack.rb_mut(),
612 Default::default(),
613 );
614 let reconstructed = reconstruct_matrix(mat.rb(), row_perm.rb());
615
616 for i in 0..m {
617 for j in 0..n {
618 assert_approx_eq!(mat_orig.read(i, j), reconstructed.read(i, j));
619 }
620 }
621 }
622 }
623}