Skip to main content

faer/linalg/cholesky/bunch_kaufman/
factor.rs

1use crate::internal_prelude::*;
2use crate::utils::cost::{ParThreshold, rate};
3use crate::{assert, perm};
4use linalg::matmul::triangular::BlockStructure;
5const TOP_BIT: usize = 1 << (usize::BITS - 1);
6/// pivoting strategy for choosing the pivots
7#[derive(Copy, Clone, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum PivotingStrategy {
10	/// deprecated, corresponds to partial pivoting
11	#[deprecated]
12	Diagonal,
13	/// searches for the k-th pivot in the k-th column
14	Partial,
15	/// searches for the k-th pivot in the k-th column, as well as the tail of
16	/// the diagonal of the matrix
17	PartialDiag,
18	/// searches for pivots that are locally optimal
19	Rook,
20	/// searches for pivots that are locally optimal, as well as the tail of the
21	/// diagonal of the matrix
22	RookDiag,
23	/// searches for pivots that are globally optimal
24	Full,
25}
26/// tuning parameters for the decomposition
27#[derive(Copy, Clone, Debug)]
28pub struct LbltParams {
29	/// pivoting strategy
30	pub pivoting: PivotingStrategy,
31	/// block size of the algorithm
32	pub block_size: usize,
33	/// floor of the parallel regions of the full-pivoting variant:
34	/// [`ParThreshold::Derived`] (the default) runs a step in parallel when its serial time
35	/// pays for one section; [`ParThreshold::Fixed`] counts elements `rem*rem` of the trailing
36	/// block
37	pub par_threshold: ParThreshold,
38	#[doc(hidden)]
39	pub non_exhaustive: NonExhaustive,
40}
41fn swap_self_adjoint<T: ComplexField>(A: MatMut<'_, T>, i: usize, j: usize) {
42	assert_ne!(i, j);
43	let mut A = A;
44	let (i, j) = (Ord::min(i, j), Ord::max(i, j));
45	perm::swap_cols_idx(A.rb_mut().get_mut(j + 1.., ..), i, j);
46	perm::swap_rows_idx(A.rb_mut().get_mut(.., ..i), i, j);
47	let tmp = A[(i, i)].real();
48	A[(i, i)] = A[(j, j)].as_real();
49	A[(j, j)] = tmp.to_cplx();
50	A[(j, i)] = A[(j, i)].conj();
51	let (Ai, Aj) = A.split_at_row_mut(j);
52	let Ai = Ai.get_mut(i + 1..j, i);
53	let Aj = Aj.get_mut(0, i + 1..j).transpose_mut();
54	zip!(Ai, Aj).for_each(|unzip!(x, y): Zip!(&mut _, &mut _)| {
55		(*x, *y) = (y.conj(), x.conj());
56	});
57}
58fn rank_1_update_and_argmax_fallback<'M, 'N, T: ComplexField>(
59	A: MatMut<'_, T, Dim<'N>, Dim<'N>>,
60	L: ColRef<'_, T, Dim<'N>>,
61	d: T::Real,
62	start: IdxInc<'N>,
63	end: IdxInc<'N>,
64) -> (usize, usize, T::Real) {
65	let mut A = A;
66	let n = A.nrows();
67	let mut max_j = n.idx(0);
68	let mut max_i = n.idx(0);
69	let mut max_offdiag = zero();
70	for j in start.to(end) {
71		for i in j.next().to(n.end()) {
72			A[(i, j)] = &A[(i, j)] - (&L[i] * L[j].conj()).mul_real(&d);
73			let val = A[(i, j)].abs2();
74			if val > max_offdiag {
75				max_offdiag = val;
76				max_i = i;
77				max_j = j;
78			}
79		}
80	}
81	(*max_i, *max_j, max_offdiag)
82}
83fn rank_2_update_and_argmax_fallback<'N, T: ComplexField>(
84	A: MatMut<'_, T, Dim<'N>, Dim<'N>>,
85	L0: ColRef<'_, T, Dim<'N>>,
86	L1: ColRef<'_, T, Dim<'N>>,
87	d: T::Real,
88	d00: T::Real,
89	d11: T::Real,
90	d10: T,
91	start: IdxInc<'N>,
92	end: IdxInc<'N>,
93) -> (usize, usize, T::Real) {
94	let mut A = A;
95	let n = A.nrows();
96	let mut max_j = n.idx(0);
97	let mut max_i = n.idx(0);
98	let mut max_offdiag = zero();
99	for j in start.to(end) {
100		let x0 = L0[j].copy();
101		let x1 = L1[j].copy();
102		let w0 = (x0.mul_real(&d11) - &x1 * &d10).mul_real(&d);
103		let w1 = (x1.mul_real(&d00) - &x0 * d10.conj()).mul_real(&d);
104		for i in j.next().to(n.end()) {
105			A[(i, j)] = &A[(i, j)] - &L0[i] * w0.conj() - &L1[i] * w1.conj();
106			let val = A[(i, j)].abs2();
107			if val > max_offdiag {
108				max_offdiag = val;
109				max_i = i;
110				max_j = j;
111			}
112		}
113	}
114	(*max_i, *max_j, max_offdiag)
115}
116fn rank_1_update_and_argmax_seq<'M, 'N, T: ComplexField>(
117	A: MatMut<'_, T, Dim<'N>, Dim<'N>>,
118	L: ColRef<'_, T, Dim<'N>>,
119	d: T::Real,
120	start: IdxInc<'N>,
121	end: IdxInc<'N>,
122) -> (usize, usize, T::Real) {
123	rank_1_update_and_argmax_fallback(A, L, d, start, end)
124}
125fn rank_2_update_and_argmax_seq<'N, T: ComplexField>(
126	A: MatMut<'_, T, Dim<'N>, Dim<'N>>,
127	L0: ColRef<'_, T, Dim<'N>>,
128	L1: ColRef<'_, T, Dim<'N>>,
129	d: T::Real,
130	d00: T::Real,
131	d11: T::Real,
132	d10: T,
133	start: IdxInc<'N>,
134	end: IdxInc<'N>,
135) -> (usize, usize, T::Real) {
136	rank_2_update_and_argmax_fallback(A, L0, L1, d, d00, d11, d10, start, end)
137}
138fn rank_1_update_and_argmax<T: ComplexField>(
139	A: MatMut<'_, T>,
140	L: ColRef<'_, T>,
141	d: T::Real,
142	par: Par,
143) -> (usize, usize, T::Real) {
144	with_dim!(N, A.nrows());
145	match par {
146		Par::Seq => rank_1_update_and_argmax_seq(
147			A.as_shape_mut(N, N),
148			L.as_row_shape(N),
149			d,
150			IdxInc::ZERO,
151			N.end(),
152		),
153		#[cfg(feature = "rayon")]
154		_ => {
155		let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
156			use rayon::prelude::*;
157			let nthreads = nthreads.get();
158			let n = *N;
159			assert!((n as u64) < (1u64 << 50));
160			let idx_to_col_start = |idx: usize| {
161				let idx_as_percent = idx as f64 / nthreads as f64;
162				let col_start_percent =
163					1.0f64 - libm::sqrt(1.0f64 - idx_as_percent);
164				(col_start_percent * n as f64) as usize
165			};
166			let mut r =
167				alloc::vec![(0usize, 0usize, zero::< T::Real > ()); nthreads];
168			crate::utils::thread::for_each(par, 
169				nthreads,
170				r.par_iter_mut().enumerate(),
171				|(idx, out)| {
172					let A = unsafe { A.rb().const_cast() };
173					let start = N.idx_inc(idx_to_col_start(idx));
174					let end = N.idx_inc(idx_to_col_start(idx + 1));
175					*out = rank_1_update_and_argmax_seq(
176						A.as_shape_mut(N, N),
177						L.as_row_shape(N),
178						d.copy(),
179						start,
180						end,
181					);
182				},
183			);
184			r.into_iter()
185				.max_by(|(_, _, a), (_, _, b)| {
186					if a == b {
187						core::cmp::Ordering::Equal
188					} else if a > b {
189						core::cmp::Ordering::Greater
190					} else {
191						core::cmp::Ordering::Less
192					}
193				})
194				.unwrap()
195		},
196	}
197}
198fn rank_2_update_and_argmax<'N, T: ComplexField>(
199	A: MatMut<'_, T>,
200	L0: ColRef<'_, T>,
201	L1: ColRef<'_, T>,
202	d: T::Real,
203	d00: T::Real,
204	d11: T::Real,
205	d10: T,
206	par: Par,
207) -> (usize, usize, T::Real) {
208	with_dim!(N, A.nrows());
209	match par {
210		Par::Seq => rank_2_update_and_argmax_seq(
211			A.as_shape_mut(N, N),
212			L0.as_row_shape(N),
213			L1.as_row_shape(N),
214			d,
215			d00,
216			d11,
217			d10,
218			IdxInc::ZERO,
219			N.end(),
220		),
221		#[cfg(feature = "rayon")]
222		_ => {
223		let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
224			use rayon::prelude::*;
225			let nthreads = nthreads.get();
226			let n = *N;
227			assert!((n as u64) < (1u64 << 50));
228			let idx_to_col_start = |idx: usize| {
229				let idx_as_percent = idx as f64 / nthreads as f64;
230				let col_start_percent =
231					1.0f64 - libm::sqrt(1.0f64 - idx_as_percent);
232				(col_start_percent * n as f64) as usize
233			};
234			let mut r =
235				alloc::vec![(0usize, 0usize, zero::< T::Real > ()); nthreads];
236			crate::utils::thread::for_each(par, 
237				nthreads,
238				r.par_iter_mut().enumerate(),
239				|(idx, out)| {
240					let A = unsafe { A.rb().const_cast() };
241					let start = N.idx_inc(idx_to_col_start(idx));
242					let end = N.idx_inc(idx_to_col_start(idx + 1));
243					*out = rank_2_update_and_argmax_seq(
244						A.as_shape_mut(N, N),
245						L0.as_row_shape(N),
246						L1.as_row_shape(N),
247						d.copy(),
248						d00.copy(),
249						d11.copy(),
250						d10.copy(),
251						start,
252						end,
253					);
254				},
255			);
256			r.into_iter()
257				.max_by(|(_, _, a), (_, _, b)| {
258					if a == b {
259						core::cmp::Ordering::Equal
260					} else if a < b {
261						core::cmp::Ordering::Less
262					} else {
263						core::cmp::Ordering::Greater
264					}
265				})
266				.unwrap()
267		},
268	}
269}
270fn lblt_full_piv<T: ComplexField>(
271	A: MatMut<'_, T>,
272	subdiag: DiagMut<'_, T>,
273	pivots: &mut [usize],
274	par: Par,
275	params: LbltParams,
276) {
277	let alpha = ((one::<T::Real>() + from_f64::<T::Real>(17.0).sqrt())
278		* from_f64::<T::Real>(0.125))
279	.abs2();
280	let mut A = A;
281	let mut subdiag = subdiag.column_vector_mut();
282	let mut par = par;
283	let n = A.nrows();
284	let scale_fwd = &A.norm_max();
285	let scale_bwd = &scale_fwd.recip();
286	zip!(A.rb_mut()).for_each(|unzip!(x)| *x = x.mul_real(scale_bwd));
287	let mut max_i = 0;
288	let mut max_j = 0;
289	let mut max_offdiag = zero();
290	for j in 0..n {
291		for i in j + 1..n {
292			let val = A[(i, j)].abs2();
293			if val > max_offdiag {
294				max_offdiag = val;
295				max_i = i;
296				max_j = j;
297			}
298		}
299	}
300	let mut k = 0;
301	while k < n {
302		if max_offdiag == zero() {
303			break;
304		}
305		let (mut Aprev, mut A) =
306			A.rb_mut().get_mut(k.., ..).split_at_col_mut(k);
307		let mut subdiag = subdiag.rb_mut().get_mut(k..);
308		let pivots = &mut pivots[k..];
309		let n = A.nrows();
310		let mut max_s = 0;
311		let mut max_diag = zero();
312		for s in 0..n {
313			let val = A[(s, s)].abs2();
314			if val > max_diag {
315				max_diag = val;
316				max_s = s;
317			}
318		}
319		let npiv;
320		let i0;
321		let i1;
322		if max_diag >= &alpha * &max_offdiag {
323			npiv = 1;
324			i0 = max_s;
325			i1 = usize::MAX;
326		} else {
327			npiv = 2;
328			i0 = max_j;
329			i1 = max_i;
330		}
331		let rem = n - npiv;
332		// One section per step (the rank-1 or rank-2 update fused with the pivot search);
333		// the unit is an element of the trailing block.
334		if !params.par_threshold.region_pays::<T>(
335			par.shape(),
336			rem * rem,
337			rate::bunch_kaufman(),
338			1,
339		) {
340			par = Par::Seq;
341		}
342		if i0 != 0 {
343			swap_self_adjoint(A.rb_mut(), 0, i0);
344			perm::swap_rows_idx(Aprev.rb_mut(), 0, i0);
345		}
346		if npiv == 2 && i1 != 1 {
347			swap_self_adjoint(A.rb_mut(), 1, i1);
348			perm::swap_rows_idx(Aprev.rb_mut(), 1, i1);
349		}
350		if npiv == 1 {
351			let diag = A[(0, 0)].real();
352			let diag_inv = &diag.recip();
353			subdiag[0] = zero();
354			let (_, _, L, mut A) = A.rb_mut().split_at_mut(1, 1);
355			let n = A.nrows();
356			let mut L = L.col_mut(0);
357			zip!(L.rb_mut()).for_each(|unzip!(x)| *x = x.mul_real(diag_inv));
358			for i in 0..n {
359				A[(i, i)] = (A[(i, i)].real() - &diag * L[i].abs2()).to_cplx();
360			}
361			if n != 0 {
362				(max_i, max_j, max_offdiag) =
363					rank_1_update_and_argmax(A.rb_mut(), L.rb(), diag, par.reborrow());
364			}
365		} else {
366			let a00 = A[(0, 0)].real();
367			let a11 = A[(1, 1)].real();
368			let a10 = A[(1, 0)].copy();
369			subdiag[0] = a10.copy();
370			subdiag[1] = zero();
371			A[(1, 0)] = zero();
372			let d10 = a10.abs();
373			let d10_inv = &d10.recip();
374			let d00 = a00 * d10_inv;
375			let d11 = a11 * d10_inv;
376			let t = (&d00 * &d11 - one::<T::Real>()).recip();
377			let d10 = a10.mul_real(d10_inv);
378			let d = t * d10_inv;
379			let (_, _, L, mut A) = A.rb_mut().split_at_mut(2, 2);
380			let (mut L0, mut L1) = L.two_cols_mut(0, 1);
381			let n = A.nrows();
382			if n != 0 {
383				(max_i, max_j, max_offdiag) = rank_2_update_and_argmax(
384					A.rb_mut(),
385					L0.rb(),
386					L1.rb(),
387					d.copy(),
388					d00.copy(),
389					d11.copy(),
390					d10.copy(),
391					par.reborrow(),
392				);
393			}
394			for j in 0..n {
395				let x0 = &L0[j].copy();
396				let x1 = &L1[j].copy();
397				let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
398				let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
399				A[(j, j)] =
400					(&A[(j, j)] - &L0[j] * w0.conj() - &L1[j] * w1.conj())
401						.as_real();
402				L0[j] = w0;
403				L1[j] = w1;
404			}
405		}
406		if npiv == 2 {
407			pivots[0] = (i0 + k) | TOP_BIT;
408			pivots[1] = (i1 + k) | TOP_BIT;
409		} else {
410			pivots[0] = i0 + k;
411		}
412		k += npiv;
413	}
414	while k < n {
415		let (mut Aprev, mut A) =
416			A.rb_mut().get_mut(k.., ..).split_at_col_mut(k);
417		let mut subdiag = subdiag.rb_mut().get_mut(k..);
418		let pivots = &mut pivots[k..];
419		let n = A.nrows();
420		let mut max_s = 0;
421		let mut max_diag = zero();
422		for s in 0..n {
423			let val = A[(s, s)].abs2();
424			if val > max_diag {
425				max_diag = val;
426				max_s = s;
427			}
428		}
429		if max_s != 0 {
430			let (mut A0, mut As) = A.rb_mut().two_cols_mut(0, max_s);
431			core::mem::swap(&mut A0[0], &mut As[max_s]);
432			perm::swap_rows_idx(Aprev.rb_mut(), 0, max_s);
433		}
434		subdiag[0] = zero();
435		pivots[0] = max_s + k;
436		k += 1;
437	}
438	zip!(A.rb_mut().diagonal_mut().column_vector_mut())
439		.for_each(|unzip!(x)| *x = x.mul_real(scale_fwd));
440	zip!(subdiag.rb_mut()).for_each(|unzip!(x)| *x = x.mul_real(scale_fwd));
441}
442#[track_caller]
443fn l1_argmax<T: ComplexField>(col: ColRef<'_, T>) -> (Option<usize>, T::Real) {
444	let n = col.nrows();
445	if n == 0 {
446		return (None, zero());
447	}
448	let mut i = 0;
449	let mut best = zero();
450	for j in 0..n {
451		let val = col[j].abs1();
452		if val > best {
453			best = val;
454			i = j;
455		}
456	}
457	(Some(i), best)
458}
459#[track_caller]
460fn offdiag_argmax<T: ComplexField>(
461	A: MatRef<'_, T>,
462	idx: usize,
463) -> (Option<usize>, T::Real) {
464	let (mut col_argmax, col_max) = l1_argmax(A.rb().get(idx + 1.., idx));
465	col_argmax.as_mut().map(|col_argmax| *col_argmax += idx + 1);
466	let (row_argmax, row_max) = l1_argmax(A.rb().get(idx, ..idx).transpose());
467	if col_max > row_max {
468		(col_argmax, col_max)
469	} else {
470		(row_argmax, row_max)
471	}
472}
473fn update_and_offdiag_argmax<T: ComplexField>(
474	mut dst: ColMut<'_, T>,
475	Wl: MatRef<'_, T>,
476	Al: MatRef<'_, T>,
477	Ar: MatRef<'_, T>,
478	i0: usize,
479	par: Par,
480) -> (Option<usize>, T::Real) {
481	let n = Al.nrows();
482	for j in 0..i0 {
483		dst[j] = Ar[(i0, j)].conj();
484	}
485	dst[i0] = zero();
486	for j in i0 + 1..n {
487		dst[j] = Ar[(j, i0)].copy();
488	}
489	linalg::matmul::matmul(
490		dst.rb_mut(),
491		Accum::Add,
492		Al.rb(),
493		Wl.row(i0).adjoint(),
494		-one::<T>(),
495		par,
496	);
497	dst[i0] = zero();
498	let ret = l1_argmax(dst.rb());
499	dst[i0] = Ar[(i0, i0)].as_real();
500	if n == 1 { (None, zero()) } else { ret }
501}
502#[inline(never)]
503fn lblt_blocked_step<T: ComplexField>(
504	alpha: T::Real,
505	W: MatMut<'_, T>,
506	A_left: MatMut<'_, T>,
507	A: MatMut<'_, T>,
508	subdiag: DiagMut<'_, T>,
509	pivots: &mut [usize],
510	rook: bool,
511	diagonal: bool,
512	mut par: Par,
513) -> usize {
514	let mut A = A;
515	let mut A_left = A_left;
516	let mut subdiag = subdiag;
517	let mut W = W;
518	let n = A.nrows();
519	let block_size = W.ncols();
520	assert!(all(
521		A.nrows() == n,
522		A.ncols() == n,
523		W.nrows() == n,
524		subdiag.dim() == n,
525		block_size >= 2,
526	));
527	let kmax = Ord::min(block_size - 1, n);
528	let mut k = 0usize;
529	while k < kmax {
530		let mut A = A.rb_mut();
531		let mut W = W.rb_mut();
532		let mut subdiag = subdiag.rb_mut().column_vector_mut().get_mut(k..);
533		let A_left = A_left.rb_mut().get_mut(k.., ..);
534		let (mut Wl, mut Wr) = W.rb_mut().get_mut(k.., ..).split_at_col_mut(k);
535		let (mut Al, mut Ar) = A.rb_mut().get_mut(k.., ..).split_at_col_mut(k);
536		let mut Al = Al.rb_mut();
537		let mut Wr = Wr.rb_mut().get_mut(.., ..2);
538		let npiv;
539		let mut i0 = if diagonal {
540			l1_argmax(Ar.rb().diagonal().column_vector()).0.unwrap()
541		} else {
542			0
543		};
544		let mut i1 = usize::MAX;
545		let mut nothing_to_do = false;
546		let (mut Wr0, mut Wr1) = Wr.rb_mut().two_cols_mut(0, 1);
547		let (r, mut gamma_i) = update_and_offdiag_argmax(
548			Wr0.rb_mut(),
549			Wl.rb(),
550			Al.rb(),
551			Ar.rb(),
552			i0,
553			par.reborrow(),
554		);
555		if k + 1 == n || gamma_i == zero() {
556			nothing_to_do = true;
557			npiv = 1;
558		} else if Ar[(i0, i0)].real().abs() >= &alpha * &gamma_i {
559			npiv = 1;
560		} else {
561			i1 = r.unwrap();
562			if rook {
563				loop {
564					let (s, gamma_r) = update_and_offdiag_argmax(
565						Wr1.rb_mut(),
566						Wl.rb(),
567						Al.rb(),
568						Ar.rb(),
569						i1,
570						par.reborrow(),
571					);
572					if Ar[(i1, i1)].abs1() >= &alpha * &gamma_r {
573						npiv = 1;
574						i0 = i1;
575						i1 = usize::MAX;
576						Wr0.copy_from(&Wr1);
577						break;
578					} else if s == Some(i0) || gamma_i == gamma_r {
579						npiv = 2;
580						break;
581					} else {
582						i0 = i1;
583						i1 = s.unwrap();
584						gamma_i = gamma_r;
585						Wr0.copy_from(&Wr1);
586					}
587				}
588			} else {
589				let (_, gamma_r) = update_and_offdiag_argmax(
590					Wr1.rb_mut(),
591					Wl.rb(),
592					Al.rb(),
593					Ar.rb(),
594					i1,
595					par.reborrow(),
596				);
597				if Ar[(i0, i0)].real().abs()
598					>= (&alpha * &gamma_r) * (&gamma_r / &gamma_i)
599				{
600					npiv = 1;
601				} else if Ar[(i1, i1)].real().abs() >= &alpha * &gamma_r {
602					npiv = 1;
603					i0 = i1;
604					i1 = usize::MAX;
605					Wr0.copy_from(&Wr1);
606				} else {
607					npiv = 2;
608				}
609			}
610		}
611		if npiv == 2 && i0 > i1 {
612			perm::swap_cols_idx(Wr.rb_mut(), 0, 1);
613			(i0, i1) = (i1, i0);
614		}
615		let mut Wr = Wr.rb_mut().get_mut(.., ..npiv);
616		'next_iter: {
617			if i0 != 0 {
618				swap_self_adjoint(Ar.rb_mut(), 0, i0);
619				perm::swap_rows_idx(Al.rb_mut(), 0, i0);
620				perm::swap_rows_idx(Wl.rb_mut(), 0, i0);
621				perm::swap_rows_idx(Wr.rb_mut(), 0, i0);
622			}
623			if npiv == 2 && i1 != 1 {
624				swap_self_adjoint(Ar.rb_mut(), 1, i1);
625				perm::swap_rows_idx(Al.rb_mut(), 1, i1);
626				perm::swap_rows_idx(Wl.rb_mut(), 1, i1);
627				perm::swap_rows_idx(Wr.rb_mut(), 1, i1);
628			}
629			if nothing_to_do {
630				break 'next_iter;
631			}
632			if npiv == 1 {
633				let W0 = Wr.rb_mut().col_mut(0);
634				let diag = &W0[0].real();
635				let diag_inv = &diag.recip();
636				subdiag[0] = zero();
637				let (_, _, L, mut A) = Ar.rb_mut().split_at_mut(1, 1);
638				let W0 = W0.rb().get(1..);
639				let n = A.nrows();
640				let mut L = L.col_mut(0);
641				zip!(W0, L.rb_mut()).for_each(
642					|unzip!(w, a): Zip!(&T, &mut T)| {
643						*a = w.mul_real(diag_inv);
644					},
645				);
646				for j in 0..n {
647					A[(j, j)] =
648						(A[(j, j)].real() - diag * &L[j].abs2()).to_cplx();
649				}
650			} else {
651				let a00 = Wr[(0, 0)].real();
652				let a11 = Wr[(1, 1)].real();
653				let a10 = Wr[(1, 0)].copy();
654				subdiag[0] = a10.copy();
655				subdiag[1] = zero();
656				Wr[(1, 0)] = zero();
657				Ar[(1, 0)] = zero();
658				let d10 = a10.abs();
659				let d10_inv = &d10.recip();
660				let d00 = a00 * d10_inv;
661				let d11 = a11 * d10_inv;
662				let t = (&d00 * &d11 - one::<T::Real>()).recip();
663				let d10 = a10.mul_real(d10_inv);
664				let d = t * d10_inv;
665				let (_, _, L, mut A) = Ar.rb_mut().split_at_mut(2, 2);
666				let (mut L0, mut L1) = L.two_cols_mut(0, 1);
667				let Wr = Wr.rb().get(2.., ..);
668				let W0 = Wr.col(0);
669				let W1 = Wr.col(1);
670				let n = A.nrows();
671				for j in 0..n {
672					let x0 = &W0[j];
673					let x1 = &W1[j];
674					let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
675					let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
676					A[(j, j)] =
677						(&A[(j, j)] - &W0[j] * w0.conj() - &W1[j] * w1.conj())
678							.as_real();
679					L0[j] = w0;
680					L1[j] = w1;
681				}
682			}
683		}
684		let offset = A_left.ncols();
685		if npiv == 2 {
686			pivots[k] = (offset + i0 + k) | TOP_BIT;
687			pivots[k + 1] = (offset + i1 + k) | TOP_BIT;
688		} else {
689			pivots[k] = offset + i0 + k;
690		}
691		k += npiv;
692	}
693	let W = W.rb().get(k.., ..k);
694	let (_, _, Al, mut Ar) = A.rb_mut().split_at_mut(k, k);
695	let Al = Al.rb();
696	linalg::matmul::triangular::matmul(
697		Ar.rb_mut(),
698		BlockStructure::StrictTriangularLower,
699		Accum::Add,
700		W,
701		BlockStructure::Rectangular,
702		Al.adjoint(),
703		BlockStructure::Rectangular,
704		-one::<T>(),
705		par,
706	);
707	for j in 0..n - k {
708		Ar[(j, j)] = Ar[(j, j)].as_real();
709	}
710	k
711}
712fn lblt_blocked<T: ComplexField>(
713	A: MatMut<'_, T>,
714	subdiag: DiagMut<'_, T>,
715	pivots: &mut [usize],
716	block_size: usize,
717	rook: bool,
718	diagonal: bool,
719	mut par: Par,
720	stack: &mut MemStack,
721) {
722	let ref alpha = (one::<T::Real>() + from_f64::<T::Real>(17.0).sqrt())
723		* from_f64::<T::Real>(0.125);
724	let mut A = A;
725	let mut subdiag = subdiag.column_vector_mut();
726	let n = A.nrows();
727	let mut k = 0;
728	while k < n {
729		let (_, _, mut A_left, A_right) = A.rb_mut().split_at_mut(k, k);
730		let (mut W, _) =
731			unsafe { temp_mat_uninit::<T, _, _>(n - k, block_size, stack) };
732		let W = W.as_mat_mut();
733		let next;
734		if block_size < 2 || n - k <= block_size {
735			lblt_unblocked(
736				alpha.copy(),
737				A_left.rb_mut(),
738				A_right,
739				subdiag.rb_mut().get_mut(k..).as_diagonal_mut(),
740				&mut pivots[k..],
741				rook,
742				diagonal,
743				par.reborrow(),
744			);
745			next = n;
746		} else {
747			let block_size = lblt_blocked_step(
748				alpha.copy(),
749				W,
750				A_left.rb_mut(),
751				A_right,
752				subdiag.rb_mut().get_mut(k..).as_diagonal_mut(),
753				&mut pivots[k..],
754				rook,
755				diagonal,
756				par.reborrow(),
757			);
758			next = k + block_size;
759		}
760		let pivots = &pivots[k..next];
761		let A_left = A.rb_mut().get_mut(.., ..k);
762		if A_left.ncols() > 0 {
763			match par {
764				Par::Seq => {
765					for mut col in A_left.col_iter_mut() {
766						for (i, &j) in core::iter::zip(k..next, pivots) {
767							let j = j & !TOP_BIT;
768							linalg::lu::partial_pivoting::factor::swap_elems(
769								col.rb_mut(),
770								i,
771								j,
772							);
773						}
774					}
775				},
776				#[cfg(feature = "rayon")]
777				_ => {
778				let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
779					let nthreads = nthreads.get();
780					crate::utils::thread::for_each(par.reborrow(), 
781						nthreads,
782						A_left.par_col_iter_mut(),
783						|mut col| {
784							for (i, &j) in core::iter::zip(k..next, pivots) {
785								let j = j & !TOP_BIT;
786								linalg::lu::partial_pivoting::factor::swap_elems(col.rb_mut(), i, j);
787							}
788						},
789					);
790				},
791			}
792		}
793		k = next;
794	}
795}
796#[inline(never)]
797fn lblt_unblocked<T: ComplexField>(
798	alpha: T::Real,
799	A_left: MatMut<'_, T>,
800	A: MatMut<'_, T>,
801	subdiag: DiagMut<'_, T>,
802	pivots: &mut [usize],
803	rook: bool,
804	diagonal: bool,
805	par: Par,
806) {
807	let _ = par;
808	let mut A = A;
809	let mut A_left = A_left;
810	let mut subdiag = subdiag;
811	let n = A.nrows();
812	assert!(all(A.nrows() == n, A.ncols() == n, subdiag.dim() == n));
813	let mut k = 0usize;
814	while k < n {
815		let (_, _, mut L_prev, mut A) = A.rb_mut().split_at_mut(k, k);
816		let mut subdiag = subdiag.rb_mut().column_vector_mut().get_mut(k..);
817		let A_left = A_left.rb_mut().get_mut(k.., ..);
818		let npiv;
819		let mut i0 = if diagonal {
820			l1_argmax(A.rb().diagonal().column_vector()).0.unwrap()
821		} else {
822			0
823		};
824		let mut i1 = usize::MAX;
825		let (r, mut gamma_i) = offdiag_argmax(A.rb(), i0);
826		let mut nothing_to_do = false;
827		if k + 1 == n || gamma_i == zero() {
828			nothing_to_do = true;
829			npiv = 1;
830		} else if A[(i0, i0)].real().abs() >= &alpha * &gamma_i {
831			npiv = 1;
832		} else {
833			i1 = r.unwrap();
834			if rook {
835				loop {
836					let (s, gamma_r) = offdiag_argmax(A.rb(), i1);
837					if A[(i1, i1)].abs1() >= &alpha * &gamma_r {
838						npiv = 1;
839						i0 = i1;
840						i1 = usize::MAX;
841						break;
842					} else if gamma_i == gamma_r {
843						npiv = 2;
844						break;
845					} else {
846						i0 = i1;
847						i1 = s.unwrap();
848						gamma_i = gamma_r;
849					}
850				}
851			} else {
852				let (_, gamma_r) = offdiag_argmax(A.rb(), i1);
853				if A[(i0, i0)].real().abs()
854					>= (&alpha * &gamma_r) * (&gamma_r / &gamma_i)
855				{
856					npiv = 1;
857				} else if A[(i1, i1)].real().abs() >= &alpha * &gamma_r {
858					npiv = 1;
859					i0 = i1;
860				} else {
861					npiv = 2;
862				}
863			}
864		}
865		if npiv == 2 && i0 > i1 {
866			(i0, i1) = (i1, i0);
867		}
868		'next_iter: {
869			if i0 != 0 {
870				swap_self_adjoint(A.rb_mut(), 0, i0);
871				perm::swap_rows_idx(L_prev.rb_mut(), 0, i0);
872			}
873			if npiv == 2 && i1 != 1 {
874				swap_self_adjoint(A.rb_mut(), 1, i1);
875				perm::swap_rows_idx(L_prev.rb_mut(), 1, i1);
876			}
877			if nothing_to_do {
878				break 'next_iter;
879			}
880			if npiv == 1 {
881				let diag = A[(0, 0)].real();
882				let diag_inv = diag.recip();
883				subdiag[0] = zero();
884				let (_, _, L, A) = A.rb_mut().split_at_mut(1, 1);
885				let L = L.col_mut(0);
886				rank1_update(A, L, diag_inv);
887			} else {
888				let a00 = A[(0, 0)].real();
889				let a11 = A[(1, 1)].real();
890				let a10 = A[(1, 0)].copy();
891				subdiag[0] = a10.copy();
892				subdiag[1] = zero();
893				A[(1, 0)] = zero();
894				let d10 = a10.abs();
895				let d10_inv = &d10.recip();
896				let d00 = a00 * d10_inv;
897				let d11 = a11 * d10_inv;
898				let t = (&d00 * &d11 - one::<T::Real>()).recip();
899				let d10 = a10.mul_real(d10_inv);
900				let d = t * d10_inv;
901				let (_, _, L, A) = A.rb_mut().split_at_mut(2, 2);
902				let (L0, L1) = L.two_cols_mut(0, 1);
903				rank2_update(A, L0, L1, d, d00, d10, d11);
904			}
905		}
906		let offset = A_left.ncols();
907		if npiv == 2 {
908			pivots[k] = (offset + i0 + k) | TOP_BIT;
909			pivots[k + 1] = (offset + i1 + k) | TOP_BIT;
910		} else {
911			pivots[k] = offset + i0 + k;
912		}
913		k += npiv;
914	}
915}
916impl<T: ComplexField> Auto<T> for LbltParams {
917	fn auto() -> Self {
918		Self {
919			pivoting: PivotingStrategy::PartialDiag,
920			block_size: 64,
921			par_threshold: ParThreshold::Derived,
922			non_exhaustive: NonExhaustive(()),
923		}
924	}
925}
926pub fn rank2_update<'a, T: ComplexField>(
927	mut A: MatMut<'a, T>,
928	mut L0: ColMut<'a, T>,
929	mut L1: ColMut<'a, T>,
930	d: T::Real,
931	d00: T::Real,
932	d10: T,
933	d11: T::Real,
934) {
935	if const { T::SIMD_CAPABILITIES.is_simd() } {
936		if let (Some(A), Some(L0), Some(L1)) = (
937			A.rb_mut().try_as_col_major_mut(),
938			L0.rb_mut().try_as_col_major_mut(),
939			L1.rb_mut().try_as_col_major_mut(),
940		) {
941			rank2_update_simd(A, L0, L1, d, d00, d10, d11);
942		} else {
943			rank2_update_fallback(A, L0, L1, d, d00, d10, d11);
944		}
945	} else {
946		rank2_update_fallback(A, L0, L1, d, d00, d10, d11);
947	}
948}
949pub fn rank2_update_simd<'a, T: ComplexField>(
950	A: MatMut<'a, T, usize, usize, ContiguousFwd>,
951	L0: ColMut<'a, T, usize, ContiguousFwd>,
952	L1: ColMut<'a, T, usize, ContiguousFwd>,
953	d: T::Real,
954	d00: T::Real,
955	d10: T,
956	d11: T::Real,
957) {
958	struct Impl<'a, T: ComplexField> {
959		A: MatMut<'a, T, usize, usize, ContiguousFwd>,
960		L0: ColMut<'a, T, usize, ContiguousFwd>,
961		L1: ColMut<'a, T, usize, ContiguousFwd>,
962		d: T::Real,
963		d00: T::Real,
964		d10: T,
965		d11: T::Real,
966	}
967	impl<T: ComplexField> pulp::WithSimd for Impl<'_, T> {
968		type Output = ();
969
970		#[inline(always)]
971		fn with_simd<S: pulp::Simd>(self, simd: S) {
972			let Self {
973				mut A,
974				mut L0,
975				mut L1,
976				d,
977				d00,
978				d10,
979				d11,
980			} = self;
981			let n = A.nrows();
982			for j in 0..n {
983				let x0 = &L0[j];
984				let x1 = &L1[j];
985				let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
986				let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
987				with_dim!({
988					let subrange_len = n - j;
989				});
990				{
991					let mut A = A
992						.rb_mut()
993						.get_mut(j.., j)
994						.as_row_shape_mut(subrange_len);
995					let L0 = L0.rb().get(j..).as_row_shape(subrange_len);
996					let L1 = L1.rb().get(j..).as_row_shape(subrange_len);
997					let simd =
998						SimdCtx::<T, S>::new(T::simd_ctx(simd), subrange_len);
999					let w0_conj = w0.conj();
1000					let w1_conj = w1.conj();
1001					let w0_conj_neg = -w0_conj;
1002					let w1_conj_neg = -w1_conj;
1003					let w0_splat = simd.splat(&w0_conj_neg);
1004					let w1_splat = simd.splat(&w1_conj_neg);
1005					simd_iter!(for i in [simd.indices()] {
1006						let mut acc = simd.read(A.rb(), i);
1007						let l0_val = simd.read(L0, i);
1008						let l1_val = simd.read(L1, i);
1009						acc = simd.mul_add(l0_val, w0_splat, acc);
1010						acc = simd.mul_add(l1_val, w1_splat, acc);
1011						simd.write(A.rb_mut(), i, acc);
1012					});
1013				}
1014				A[(j, j)] = A[(j, j)].real().to_cplx();
1015				L0[j] = w0;
1016				L1[j] = w1;
1017			}
1018		}
1019	}
1020	dispatch!(
1021		Impl {
1022			A,
1023			L0,
1024			L1,
1025			d,
1026			d00,
1027			d10,
1028			d11
1029		},
1030		Impl,
1031		T
1032	)
1033}
1034pub fn rank2_update_fallback<'a, T: ComplexField>(
1035	mut A: MatMut<'a, T>,
1036	mut L0: ColMut<'a, T>,
1037	mut L1: ColMut<'a, T>,
1038	d: T::Real,
1039	d00: T::Real,
1040	d10: T,
1041	d11: T::Real,
1042) {
1043	let n = A.nrows();
1044	for j in 0..n {
1045		let x0 = &L0[j].copy();
1046		let x1 = &L1[j].copy();
1047		let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
1048		let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
1049		for i in j..n {
1050			A[(i, j)] = &A[(i, j)] - &L0[i] * w0.conj() - &L1[i] * w1.conj();
1051		}
1052		A[(j, j)] = A[(j, j)].as_real();
1053		L0[j] = w0;
1054		L1[j] = w1;
1055	}
1056}
1057pub fn rank1_update<'a, T: ComplexField>(
1058	mut A: MatMut<'a, T>,
1059	mut L0: ColMut<'a, T>,
1060	d: T::Real,
1061) {
1062	if const { T::SIMD_CAPABILITIES.is_simd() } {
1063		if let (Some(A), Some(L0)) = (
1064			A.rb_mut().try_as_col_major_mut(),
1065			L0.rb_mut().try_as_col_major_mut(),
1066		) {
1067			rank1_update_simd(A, L0, d);
1068		} else {
1069			rank1_update_fallback(A, L0, d);
1070		}
1071	} else {
1072		rank1_update_fallback(A, L0, d);
1073	}
1074}
1075pub fn rank1_update_simd<'a, T: ComplexField>(
1076	A: MatMut<'a, T, usize, usize, ContiguousFwd>,
1077	L0: ColMut<'a, T, usize, ContiguousFwd>,
1078	d: T::Real,
1079) {
1080	struct Impl<'a, T: ComplexField> {
1081		A: MatMut<'a, T, usize, usize, ContiguousFwd>,
1082		L0: ColMut<'a, T, usize, ContiguousFwd>,
1083		d: T::Real,
1084	}
1085	impl<T: ComplexField> pulp::WithSimd for Impl<'_, T> {
1086		type Output = ();
1087
1088		#[inline(always)]
1089		fn with_simd<S: pulp::Simd>(self, simd: S) {
1090			let Self { mut A, mut L0, d } = self;
1091			let n = A.nrows();
1092			for j in 0..n {
1093				let x0 = &L0[j];
1094				let w0 = x0.mul_real(&d);
1095				with_dim!({
1096					let subrange_len = n - j;
1097				});
1098				{
1099					let mut A = A
1100						.rb_mut()
1101						.get_mut(j.., j)
1102						.as_row_shape_mut(subrange_len);
1103					let L0 = L0.rb().get(j..).as_row_shape(subrange_len);
1104					let simd =
1105						SimdCtx::<T, S>::new(T::simd_ctx(simd), subrange_len);
1106					let w0_conj = w0.conj();
1107					let w0_conj_neg = -w0_conj;
1108					let w0_splat = simd.splat(&w0_conj_neg);
1109					simd_iter!(for i in [simd.indices()] {
1110						let mut acc = simd.read(A.rb(), i);
1111						let l0_val = simd.read(L0, i);
1112						acc = simd.mul_add(l0_val, w0_splat, acc);
1113						simd.write(A.rb_mut(), i, acc);
1114					});
1115				}
1116				A[(j, j)] = A[(j, j)].real().to_cplx();
1117				L0[j] = w0;
1118			}
1119		}
1120	}
1121	dispatch!(Impl { A, L0, d }, Impl, T)
1122}
1123pub fn rank1_update_fallback<'a, T: ComplexField>(
1124	mut A: MatMut<'a, T>,
1125	mut L0: ColMut<'a, T>,
1126	d: T::Real,
1127) {
1128	let n = A.nrows();
1129	for j in 0..n {
1130		let x0 = &L0[j];
1131		let w0 = x0.mul_real(&d);
1132		for i in j..n {
1133			A[(i, j)] = &A[(i, j)] - &L0[i] * w0.conj();
1134		}
1135		A[(j, j)] = A[(j, j)].real().to_cplx();
1136		L0[j] = w0;
1137	}
1138}
1139/// computes the layout of required workspace for performing an $LBL^\top$
1140/// decomposition
1141pub fn cholesky_in_place_scratch<I: Index, T: ComplexField>(
1142	dim: usize,
1143	par: ParShape,
1144	params: Spec<LbltParams, T>,
1145) -> StackReq {
1146	let params = params.config;
1147	let _ = par;
1148	let mut bs = params.block_size;
1149	if bs < 2 || dim <= bs {
1150		bs = 0;
1151	}
1152	StackReq::new::<usize>(dim).and(temp_mat_scratch::<T>(dim, bs))
1153}
1154/// info about the result of the $LBL^\top$ factorization
1155#[derive(Copy, Clone, Debug)]
1156pub struct LbltInfo {
1157	/// number of pivoting transpositions
1158	pub transposition_count: usize,
1159}
1160/// computes the $LBL^\top$ factorization of $A$ and stores the factorization in
1161/// `matrix` and `subdiag`
1162///
1163/// the diagonal of the block diagonal matrix is stored on the diagonal
1164/// of `matrix`, while the subdiagonal elements of the blocks are stored in
1165/// `subdiag`
1166///
1167/// # panics
1168///
1169/// panics if the input matrix is not square
1170///
1171/// this can also panic if the provided memory in `stack` is insufficient (see
1172/// [`cholesky_in_place_scratch`]).
1173#[track_caller]
1174pub fn cholesky_in_place<'out, I: Index, T: ComplexField>(
1175	A: MatMut<'_, T>,
1176	subdiag: DiagMut<'_, T>,
1177	perm: &'out mut [I],
1178	perm_inv: &'out mut [I],
1179	par: Par,
1180	stack: &mut MemStack,
1181	params: Spec<LbltParams, T>,
1182) -> (LbltInfo, PermRef<'out, I>) {
1183	let params = params.config;
1184	let truncate = <I::Signed as SignedIndex>::truncate;
1185	let n = A.nrows();
1186	assert!(all(
1187		A.nrows() == A.ncols(),
1188		subdiag.dim() == n,
1189		perm.len() == n,
1190		perm_inv.len() == n
1191	));
1192	#[cfg(feature = "perf-warn")]
1193	if A.row_stride().unsigned_abs() != 1 && crate::__perf_warn!(CHOLESKY_WARN)
1194	{
1195		if A.col_stride().unsigned_abs() == 1 {
1196			log::warn!(
1197				target : "faer_perf",
1198				"$LBL^\top$ decomposition prefers column-major
1199			matrix. Found row-major matrix."
1200			);
1201		} else {
1202			log::warn!(
1203				target : "faer_perf",
1204				"$LBL^\top$ decomposition prefers column-major
1205			matrix. Found matrix with generic strides."
1206			);
1207		}
1208	}
1209	let (mut pivots, stack) = stack.make_with::<usize>(n, |_| 0);
1210	let pivots = &mut *pivots;
1211	let mut bs = params.block_size;
1212	if bs < 2 || n <= bs {
1213		bs = 0;
1214	}
1215	let (rook, diagonal) = match params.pivoting {
1216		PivotingStrategy::Partial => (false, false),
1217		PivotingStrategy::PartialDiag => (false, true),
1218		PivotingStrategy::Rook => (true, false),
1219		PivotingStrategy::RookDiag => (true, true),
1220		_ => (false, false),
1221	};
1222	if params.pivoting == PivotingStrategy::Full {
1223		lblt_full_piv(A, subdiag, pivots, par, params);
1224	} else {
1225		lblt_blocked(A, subdiag, pivots, bs, rook, diagonal, par, stack);
1226	}
1227	for (i, p) in perm.iter_mut().enumerate() {
1228		*p = I::from_signed(truncate(i));
1229	}
1230	let mut transposition_count = 0usize;
1231	for i in 0..n {
1232		let p = pivots[i] & !TOP_BIT;
1233		if i != p {
1234			transposition_count += 1;
1235		}
1236		perm.swap(i, p);
1237	}
1238	for (i, &p) in perm.iter().enumerate() {
1239		perm_inv[p.to_signed().zx()] = I::from_signed(truncate(i));
1240	}
1241	(
1242		LbltInfo {
1243			transposition_count,
1244		},
1245		unsafe { PermRef::new_unchecked(perm, perm_inv, n) },
1246	)
1247}