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 {
104 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
105 A00.rb(),
106 A01.rb_mut(),
107 par,
108 );
109 }
110 linalg::matmul::matmul(
111 A11.rb_mut(),
112 Accum::Add,
113 A10.rb(),
114 A01.rb(),
115 -one::<T>(),
116 par,
117 );
118 n_trans += lu_in_place_recursion(
119 A.rb_mut().get_mut(block_size..m, ..),
120 block_size,
121 n,
122 &mut trans[block_size..n],
123 par,
124 params.into(),
125 );
126 }
127 let swap = |mat: MatMut<'_, T>| {
128 let mut mat = mat;
129 for j in 0..mat.ncols() {
130 let mut col = mat.rb_mut().col_mut(j);
131 if col.row_stride() == 1 {
132 for (j, &t) in trans[..n].iter().enumerate() {
133 swap_elems(col.rb_mut(), j, t.zx() + j);
134 }
135 } else {
136 for (j, &t) in trans[..n].iter().enumerate() {
137 swap_elems(col.rb_mut(), j, t.zx() + j);
138 }
139 }
140 }
141 };
142 let (A_left, A_right) = A.rb_mut().split_at_col_mut(start);
143 let A_right = A_right.get_mut(.., end - start..ncols - start);
144 let par = if m * (ncols - n) > params.par_threshold {
145 par
146 } else {
147 Par::Seq
148 };
149 match par {
150 Par::Seq => {
151 swap(A_left);
152 swap(A_right);
153 },
154 #[cfg(feature = "rayon")]
155 Par::Rayon(nthreads) => {
156 let nthreads = nthreads.get();
157 let len = (A_left.ncols() + A_right.ncols()) as f64;
158 let left_threads = Ord::min(
159 (nthreads as f64 * (A_left.ncols() as f64 / len)) as usize,
160 nthreads,
161 );
162 let right_threads = nthreads - left_threads;
163 crate::utils::thread::join_raw(
164 |_| {
165 if A_left.ncols() > 0 {
166 spindle::for_each(
167 left_threads,
168 A_left.par_col_partition_mut(left_threads),
169 |A| swap(A),
170 )
171 }
172 },
173 |_| {
174 if A_right.ncols() > 0 {
175 spindle::for_each(
176 right_threads,
177 A_right.par_col_partition_mut(right_threads),
178 |A| swap(A),
179 )
180 }
181 },
182 par,
183 );
184 },
185 }
186 n_trans
187}
188#[derive(Copy, Clone, Debug)]
190pub struct PartialPivLuParams {
191 pub recursion_threshold: usize,
193 pub block_size: usize,
195 pub par_threshold: usize,
197 #[doc(hidden)]
198 pub non_exhaustive: NonExhaustive,
199}
200#[derive(Copy, Clone, Debug)]
202pub struct PartialPivLuInfo {
203 pub transposition_count: usize,
206}
207#[derive(Copy, Clone, Debug)]
209pub enum LdltError {
210 ZeroPivot { index: usize },
211}
212impl<T: ComplexField> Auto<T> for PartialPivLuParams {
213 #[inline]
214 fn auto() -> Self {
215 Self {
216 recursion_threshold: 16,
217 block_size: 64,
218 par_threshold: 128 * 128,
219 non_exhaustive: NonExhaustive(()),
220 }
221 }
222}
223#[inline]
224pub fn lu_in_place_scratch<I: Index, T: ComplexField>(
225 nrows: usize,
226 ncols: usize,
227 par: Par,
228 params: Spec<PartialPivLuParams, T>,
229) -> StackReq {
230 _ = par;
231 _ = params;
232 StackReq::new::<I>(Ord::min(nrows, ncols))
233}
234pub fn lu_in_place<'out, I: Index, T: ComplexField>(
235 A: MatMut<'_, T>,
236 perm: &'out mut [I],
237 perm_inv: &'out mut [I],
238 par: Par,
239 stack: &mut MemStack,
240 params: Spec<PartialPivLuParams, T>,
241) -> (PartialPivLuInfo, PermRef<'out, I>) {
242 let _ = ¶ms;
243 let truncate = I::truncate;
244 #[cfg(feature = "perf-warn")]
245 if (A.col_stride().unsigned_abs() == 1
246 || A.row_stride().unsigned_abs() != 1)
247 && crate::__perf_warn!(LU_WARN)
248 {
249 log::warn!(
250 target : "faer_perf",
251 "LU with partial pivoting prefers column-major or row-major matrix. Found matrix with generic strides."
252 );
253 }
254 let mut matrix = A;
255 let mut stack = stack;
256 let m = matrix.nrows();
257 let n = matrix.ncols();
258 let size = Ord::min(n, m);
259 for i in 0..m {
260 let p = &mut perm[i];
261 *p = truncate(i);
262 }
263 let (mut transpositions, _) =
264 stack.rb_mut().make_with(size, |_| truncate(0));
265 let transpositions = transpositions.as_mut();
266 let n_transpositions = lu_in_place_recursion(
267 matrix.rb_mut(),
268 0,
269 size,
270 transpositions.as_mut(),
271 par,
272 params,
273 );
274 for idx in 0..size {
275 let t = transpositions[idx];
276 perm.as_mut().swap(idx, idx + t.zx());
277 }
278 if m < n {
279 let (left, right) = matrix.split_at_col_mut(size);
280 linalg::triangular_solve::solve_unit_lower_triangular_in_place(
281 left.rb(),
282 right,
283 par,
284 );
285 }
286 for i in 0..m {
287 perm_inv[perm[i].zx()] = truncate(i);
288 }
289 (
290 PartialPivLuInfo {
291 transposition_count: n_transpositions,
292 },
293 unsafe { PermRef::new_unchecked(perm, perm_inv, m) },
294 )
295}
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::stats::prelude::*;
300 use crate::utils::approx::*;
301 use crate::{Mat, assert};
302 use dyn_stack::MemBuffer;
303 #[test]
304 fn test_plu() {
305 let rng = &mut StdRng::seed_from_u64(0);
306 let approx_eq = CwiseMat(ApproxEq {
307 abs_tol: 1e-13,
308 rel_tol: 1e-13,
309 });
310 for n in [1, 2, 3, 128, 255, 256, 257] {
311 let A = CwiseMatDistribution {
312 nrows: n,
313 ncols: n,
314 dist: StandardNormal,
315 }
316 .rand::<Mat<f64>>(rng);
317 let A = A.as_ref();
318 let mut LU = A.cloned();
319 let perm = &mut *vec![0usize; n];
320 let perm_inv = &mut *vec![0usize; n];
321 let params = PartialPivLuParams {
322 recursion_threshold: 2,
323 block_size: 2,
324 ..auto!(f64)
325 };
326 let p = lu_in_place(
327 LU.as_mut(),
328 perm,
329 perm_inv,
330 Par::Seq,
331 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
332 usize,
333 f64,
334 >(
335 n,
336 n,
337 Par::Seq,
338 params.into(),
339 ))),
340 params.into(),
341 )
342 .1;
343 let mut L = LU.as_ref().cloned();
344 let mut U = LU.as_ref().cloned();
345 for j in 0..n {
346 for i in 0..j {
347 L[(i, j)] = 0.0;
348 }
349 L[(j, j)] = 1.0;
350 }
351 for j in 0..n {
352 for i in j + 1..n {
353 U[(i, j)] = 0.0;
354 }
355 }
356 let L = L.as_ref();
357 let U = U.as_ref();
358 assert!(p.inverse() * L * U ~ A);
359 }
360 for m in [8, 128, 255, 256, 257] {
361 let n = 8;
362 let A = CwiseMatDistribution {
363 nrows: m,
364 ncols: n,
365 dist: StandardNormal,
366 }
367 .rand::<Mat<f64>>(rng);
368 let A = A.as_ref();
369 let mut LU = A.cloned();
370 let perm = &mut *vec![0usize; m];
371 let perm_inv = &mut *vec![0usize; m];
372 let p = lu_in_place(
373 LU.as_mut(),
374 perm,
375 perm_inv,
376 Par::Seq,
377 MemStack::new(&mut MemBuffer::new(lu_in_place_scratch::<
378 usize,
379 f64,
380 >(
381 n, n, Par::Seq, default()
382 ))),
383 default(),
384 )
385 .1;
386 let mut L = LU.as_ref().cloned();
387 let mut U = LU.as_ref().cloned();
388 for j in 0..n {
389 for i in 0..j {
390 L[(i, j)] = 0.0;
391 }
392 L[(j, j)] = 1.0;
393 }
394 for j in 0..n {
395 for i in j + 1..m {
396 U[(i, j)] = 0.0;
397 }
398 }
399 let L = L.as_ref();
400 let U = U.as_ref();
401 let U = U.subrows(0, n);
402 assert!(p.inverse() * L * U ~ A);
403 }
404 }
405}