Skip to main content

faer/linalg/lu/partial_pivoting/
factor.rs

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/// $LU$ factorization tuning parameters
189#[derive(Copy, Clone, Debug)]
190pub struct PartialPivLuParams {
191	/// threshold at which the implementation should stop recursing
192	pub recursion_threshold: usize,
193	/// blocking variant step size
194	pub block_size: usize,
195	/// threshold at which size parallelism should be disabled
196	pub par_threshold: usize,
197	#[doc(hidden)]
198	pub non_exhaustive: NonExhaustive,
199}
200/// information about the resulting $LU$ factorization
201#[derive(Copy, Clone, Debug)]
202pub struct PartialPivLuInfo {
203	/// number of transpositions that were performed, can be used to compute
204	/// the determinant of $P$
205	pub transposition_count: usize,
206}
207/// error in the $LU$ factorization
208#[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 _ = &params;
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}