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				subdiag[0] = zero();
631				break 'next_iter;
632			}
633			if npiv == 1 {
634				let W0 = Wr.rb_mut().col_mut(0);
635				let diag = &W0[0].real();
636				let diag_inv = &diag.recip();
637				subdiag[0] = zero();
638				let (_, _, L, mut A) = Ar.rb_mut().split_at_mut(1, 1);
639				let W0 = W0.rb().get(1..);
640				let n = A.nrows();
641				let mut L = L.col_mut(0);
642				zip!(W0, L.rb_mut()).for_each(
643					|unzip!(w, a): Zip!(&T, &mut T)| {
644						*a = w.mul_real(diag_inv);
645					},
646				);
647				for j in 0..n {
648					A[(j, j)] =
649						(A[(j, j)].real() - diag * &L[j].abs2()).to_cplx();
650				}
651			} else {
652				let a00 = Wr[(0, 0)].real();
653				let a11 = Wr[(1, 1)].real();
654				let a10 = Wr[(1, 0)].copy();
655				subdiag[0] = a10.copy();
656				subdiag[1] = zero();
657				Wr[(1, 0)] = zero();
658				Ar[(1, 0)] = zero();
659				let d10 = a10.abs();
660				let d10_inv = &d10.recip();
661				let d00 = a00 * d10_inv;
662				let d11 = a11 * d10_inv;
663				let t = (&d00 * &d11 - one::<T::Real>()).recip();
664				let d10 = a10.mul_real(d10_inv);
665				let d = t * d10_inv;
666				let (_, _, L, mut A) = Ar.rb_mut().split_at_mut(2, 2);
667				let (mut L0, mut L1) = L.two_cols_mut(0, 1);
668				let Wr = Wr.rb().get(2.., ..);
669				let W0 = Wr.col(0);
670				let W1 = Wr.col(1);
671				let n = A.nrows();
672				for j in 0..n {
673					let x0 = &W0[j];
674					let x1 = &W1[j];
675					let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
676					let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
677					A[(j, j)] =
678						(&A[(j, j)] - &W0[j] * w0.conj() - &W1[j] * w1.conj())
679							.as_real();
680					L0[j] = w0;
681					L1[j] = w1;
682				}
683			}
684		}
685		let offset = A_left.ncols();
686		if npiv == 2 {
687			pivots[k] = (offset + i0 + k) | TOP_BIT;
688			pivots[k + 1] = (offset + i1 + k) | TOP_BIT;
689		} else {
690			pivots[k] = offset + i0 + k;
691		}
692		k += npiv;
693	}
694	let W = W.rb().get(k.., ..k);
695	let (_, _, Al, mut Ar) = A.rb_mut().split_at_mut(k, k);
696	let Al = Al.rb();
697	linalg::matmul::triangular::matmul(
698		Ar.rb_mut(),
699		BlockStructure::StrictTriangularLower,
700		Accum::Add,
701		W,
702		BlockStructure::Rectangular,
703		Al.adjoint(),
704		BlockStructure::Rectangular,
705		-one::<T>(),
706		par,
707	);
708	for j in 0..n - k {
709		Ar[(j, j)] = Ar[(j, j)].as_real();
710	}
711	k
712}
713fn lblt_blocked<T: ComplexField>(
714	A: MatMut<'_, T>,
715	subdiag: DiagMut<'_, T>,
716	pivots: &mut [usize],
717	block_size: usize,
718	rook: bool,
719	diagonal: bool,
720	mut par: Par,
721	stack: &mut MemStack,
722) {
723	let ref alpha = (one::<T::Real>() + from_f64::<T::Real>(17.0).sqrt())
724		* from_f64::<T::Real>(0.125);
725	let mut A = A;
726	let mut subdiag = subdiag.column_vector_mut();
727	let n = A.nrows();
728	let mut k = 0;
729	while k < n {
730		let (_, _, mut A_left, A_right) = A.rb_mut().split_at_mut(k, k);
731		let (mut W, _) =
732			unsafe { temp_mat_uninit::<T, _, _>(n - k, block_size, stack) };
733		let W = W.as_mat_mut();
734		let next;
735		if block_size < 2 || n - k <= block_size {
736			lblt_unblocked(
737				alpha.copy(),
738				A_left.rb_mut(),
739				A_right,
740				subdiag.rb_mut().get_mut(k..).as_diagonal_mut(),
741				&mut pivots[k..],
742				rook,
743				diagonal,
744				par.reborrow(),
745			);
746			next = n;
747		} else {
748			let block_size = lblt_blocked_step(
749				alpha.copy(),
750				W,
751				A_left.rb_mut(),
752				A_right,
753				subdiag.rb_mut().get_mut(k..).as_diagonal_mut(),
754				&mut pivots[k..],
755				rook,
756				diagonal,
757				par.reborrow(),
758			);
759			next = k + block_size;
760		}
761		let pivots = &pivots[k..next];
762		let A_left = A.rb_mut().get_mut(.., ..k);
763		if A_left.ncols() > 0 {
764			match par {
765				Par::Seq => {
766					for mut col in A_left.col_iter_mut() {
767						for (i, &j) in core::iter::zip(k..next, pivots) {
768							let j = j & !TOP_BIT;
769							linalg::lu::partial_pivoting::factor::swap_elems(
770								col.rb_mut(),
771								i,
772								j,
773							);
774						}
775					}
776				},
777				#[cfg(feature = "rayon")]
778				_ => {
779				let nthreads = core::num::NonZeroUsize::new(par.degree()).unwrap();
780					let nthreads = nthreads.get();
781					crate::utils::thread::for_each(par.reborrow(), 
782						nthreads,
783						A_left.par_col_iter_mut(),
784						|mut col| {
785							for (i, &j) in core::iter::zip(k..next, pivots) {
786								let j = j & !TOP_BIT;
787								linalg::lu::partial_pivoting::factor::swap_elems(col.rb_mut(), i, j);
788							}
789						},
790					);
791				},
792			}
793		}
794		k = next;
795	}
796}
797#[inline(never)]
798fn lblt_unblocked<T: ComplexField>(
799	alpha: T::Real,
800	A_left: MatMut<'_, T>,
801	A: MatMut<'_, T>,
802	subdiag: DiagMut<'_, T>,
803	pivots: &mut [usize],
804	rook: bool,
805	diagonal: bool,
806	par: Par,
807) {
808	let _ = par;
809	let mut A = A;
810	let mut A_left = A_left;
811	let mut subdiag = subdiag;
812	let n = A.nrows();
813	assert!(all(A.nrows() == n, A.ncols() == n, subdiag.dim() == n));
814	let mut k = 0usize;
815	while k < n {
816		let (_, _, mut L_prev, mut A) = A.rb_mut().split_at_mut(k, k);
817		let mut subdiag = subdiag.rb_mut().column_vector_mut().get_mut(k..);
818		let A_left = A_left.rb_mut().get_mut(k.., ..);
819		let npiv;
820		let mut i0 = if diagonal {
821			l1_argmax(A.rb().diagonal().column_vector()).0.unwrap()
822		} else {
823			0
824		};
825		let mut i1 = usize::MAX;
826		let (r, mut gamma_i) = offdiag_argmax(A.rb(), i0);
827		let mut nothing_to_do = false;
828		if k + 1 == n || gamma_i == zero() {
829			nothing_to_do = true;
830			npiv = 1;
831		} else if A[(i0, i0)].real().abs() >= &alpha * &gamma_i {
832			npiv = 1;
833		} else {
834			i1 = r.unwrap();
835			if rook {
836				loop {
837					let (s, gamma_r) = offdiag_argmax(A.rb(), i1);
838					if A[(i1, i1)].abs1() >= &alpha * &gamma_r {
839						npiv = 1;
840						i0 = i1;
841						i1 = usize::MAX;
842						break;
843					} else if gamma_i == gamma_r {
844						npiv = 2;
845						break;
846					} else {
847						i0 = i1;
848						i1 = s.unwrap();
849						gamma_i = gamma_r;
850					}
851				}
852			} else {
853				let (_, gamma_r) = offdiag_argmax(A.rb(), i1);
854				if A[(i0, i0)].real().abs()
855					>= (&alpha * &gamma_r) * (&gamma_r / &gamma_i)
856				{
857					npiv = 1;
858				} else if A[(i1, i1)].real().abs() >= &alpha * &gamma_r {
859					npiv = 1;
860					i0 = i1;
861				} else {
862					npiv = 2;
863				}
864			}
865		}
866		if npiv == 2 && i0 > i1 {
867			(i0, i1) = (i1, i0);
868		}
869		'next_iter: {
870			if i0 != 0 {
871				swap_self_adjoint(A.rb_mut(), 0, i0);
872				perm::swap_rows_idx(L_prev.rb_mut(), 0, i0);
873			}
874			if npiv == 2 && i1 != 1 {
875				swap_self_adjoint(A.rb_mut(), 1, i1);
876				perm::swap_rows_idx(L_prev.rb_mut(), 1, i1);
877			}
878			if nothing_to_do {
879				subdiag[0] = zero();
880				break 'next_iter;
881			}
882			if npiv == 1 {
883				let diag = A[(0, 0)].real();
884				let diag_inv = diag.recip();
885				subdiag[0] = zero();
886				let (_, _, L, A) = A.rb_mut().split_at_mut(1, 1);
887				let L = L.col_mut(0);
888				rank1_update(A, L, diag_inv);
889			} else {
890				let a00 = A[(0, 0)].real();
891				let a11 = A[(1, 1)].real();
892				let a10 = A[(1, 0)].copy();
893				subdiag[0] = a10.copy();
894				subdiag[1] = zero();
895				A[(1, 0)] = zero();
896				let d10 = a10.abs();
897				let d10_inv = &d10.recip();
898				let d00 = a00 * d10_inv;
899				let d11 = a11 * d10_inv;
900				let t = (&d00 * &d11 - one::<T::Real>()).recip();
901				let d10 = a10.mul_real(d10_inv);
902				let d = t * d10_inv;
903				let (_, _, L, A) = A.rb_mut().split_at_mut(2, 2);
904				let (L0, L1) = L.two_cols_mut(0, 1);
905				rank2_update(A, L0, L1, d, d00, d10, d11);
906			}
907		}
908		let offset = A_left.ncols();
909		if npiv == 2 {
910			pivots[k] = (offset + i0 + k) | TOP_BIT;
911			pivots[k + 1] = (offset + i1 + k) | TOP_BIT;
912		} else {
913			pivots[k] = offset + i0 + k;
914		}
915		k += npiv;
916	}
917}
918impl<T: ComplexField> Auto<T> for LbltParams {
919	fn auto() -> Self {
920		Self {
921			pivoting: PivotingStrategy::PartialDiag,
922			block_size: 64,
923			par_threshold: ParThreshold::Derived,
924			non_exhaustive: NonExhaustive(()),
925		}
926	}
927}
928pub fn rank2_update<'a, T: ComplexField>(
929	mut A: MatMut<'a, T>,
930	mut L0: ColMut<'a, T>,
931	mut L1: ColMut<'a, T>,
932	d: T::Real,
933	d00: T::Real,
934	d10: T,
935	d11: T::Real,
936) {
937	if const { T::SIMD_CAPABILITIES.is_simd() } {
938		if let (Some(A), Some(L0), Some(L1)) = (
939			A.rb_mut().try_as_col_major_mut(),
940			L0.rb_mut().try_as_col_major_mut(),
941			L1.rb_mut().try_as_col_major_mut(),
942		) {
943			rank2_update_simd(A, L0, L1, d, d00, d10, d11);
944		} else {
945			rank2_update_fallback(A, L0, L1, d, d00, d10, d11);
946		}
947	} else {
948		rank2_update_fallback(A, L0, L1, d, d00, d10, d11);
949	}
950}
951pub fn rank2_update_simd<'a, T: ComplexField>(
952	A: MatMut<'a, T, usize, usize, ContiguousFwd>,
953	L0: ColMut<'a, T, usize, ContiguousFwd>,
954	L1: ColMut<'a, T, usize, ContiguousFwd>,
955	d: T::Real,
956	d00: T::Real,
957	d10: T,
958	d11: T::Real,
959) {
960	struct Impl<'a, T: ComplexField> {
961		A: MatMut<'a, T, usize, usize, ContiguousFwd>,
962		L0: ColMut<'a, T, usize, ContiguousFwd>,
963		L1: ColMut<'a, T, usize, ContiguousFwd>,
964		d: T::Real,
965		d00: T::Real,
966		d10: T,
967		d11: T::Real,
968	}
969	impl<T: ComplexField> pulp::WithSimd for Impl<'_, T> {
970		type Output = ();
971
972		#[inline(always)]
973		fn with_simd<S: pulp::Simd>(self, simd: S) {
974			let Self {
975				mut A,
976				mut L0,
977				mut L1,
978				d,
979				d00,
980				d10,
981				d11,
982			} = self;
983			let n = A.nrows();
984			for j in 0..n {
985				let x0 = &L0[j];
986				let x1 = &L1[j];
987				let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
988				let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
989				with_dim!({
990					let subrange_len = n - j;
991				});
992				{
993					let mut A = A
994						.rb_mut()
995						.get_mut(j.., j)
996						.as_row_shape_mut(subrange_len);
997					let L0 = L0.rb().get(j..).as_row_shape(subrange_len);
998					let L1 = L1.rb().get(j..).as_row_shape(subrange_len);
999					let simd =
1000						SimdCtx::<T, S>::new(T::simd_ctx(simd), subrange_len);
1001					let w0_conj = w0.conj();
1002					let w1_conj = w1.conj();
1003					let w0_conj_neg = -w0_conj;
1004					let w1_conj_neg = -w1_conj;
1005					let w0_splat = simd.splat(&w0_conj_neg);
1006					let w1_splat = simd.splat(&w1_conj_neg);
1007					simd_iter!(for i in [simd.indices()] {
1008						let mut acc = simd.read(A.rb(), i);
1009						let l0_val = simd.read(L0, i);
1010						let l1_val = simd.read(L1, i);
1011						acc = simd.mul_add(l0_val, w0_splat, acc);
1012						acc = simd.mul_add(l1_val, w1_splat, acc);
1013						simd.write(A.rb_mut(), i, acc);
1014					});
1015				}
1016				A[(j, j)] = A[(j, j)].real().to_cplx();
1017				L0[j] = w0;
1018				L1[j] = w1;
1019			}
1020		}
1021	}
1022	dispatch!(
1023		Impl {
1024			A,
1025			L0,
1026			L1,
1027			d,
1028			d00,
1029			d10,
1030			d11
1031		},
1032		Impl,
1033		T
1034	)
1035}
1036pub fn rank2_update_fallback<'a, T: ComplexField>(
1037	mut A: MatMut<'a, T>,
1038	mut L0: ColMut<'a, T>,
1039	mut L1: ColMut<'a, T>,
1040	d: T::Real,
1041	d00: T::Real,
1042	d10: T,
1043	d11: T::Real,
1044) {
1045	let n = A.nrows();
1046	for j in 0..n {
1047		let x0 = &L0[j].copy();
1048		let x1 = &L1[j].copy();
1049		let w0 = (x0.mul_real(&d11) - x1 * &d10).mul_real(&d);
1050		let w1 = (x1.mul_real(&d00) - x0 * d10.conj()).mul_real(&d);
1051		for i in j..n {
1052			A[(i, j)] = &A[(i, j)] - &L0[i] * w0.conj() - &L1[i] * w1.conj();
1053		}
1054		A[(j, j)] = A[(j, j)].as_real();
1055		L0[j] = w0;
1056		L1[j] = w1;
1057	}
1058}
1059pub fn rank1_update<'a, T: ComplexField>(
1060	mut A: MatMut<'a, T>,
1061	mut L0: ColMut<'a, T>,
1062	d: T::Real,
1063) {
1064	if const { T::SIMD_CAPABILITIES.is_simd() } {
1065		if let (Some(A), Some(L0)) = (
1066			A.rb_mut().try_as_col_major_mut(),
1067			L0.rb_mut().try_as_col_major_mut(),
1068		) {
1069			rank1_update_simd(A, L0, d);
1070		} else {
1071			rank1_update_fallback(A, L0, d);
1072		}
1073	} else {
1074		rank1_update_fallback(A, L0, d);
1075	}
1076}
1077pub fn rank1_update_simd<'a, T: ComplexField>(
1078	A: MatMut<'a, T, usize, usize, ContiguousFwd>,
1079	L0: ColMut<'a, T, usize, ContiguousFwd>,
1080	d: T::Real,
1081) {
1082	struct Impl<'a, T: ComplexField> {
1083		A: MatMut<'a, T, usize, usize, ContiguousFwd>,
1084		L0: ColMut<'a, T, usize, ContiguousFwd>,
1085		d: T::Real,
1086	}
1087	impl<T: ComplexField> pulp::WithSimd for Impl<'_, T> {
1088		type Output = ();
1089
1090		#[inline(always)]
1091		fn with_simd<S: pulp::Simd>(self, simd: S) {
1092			let Self { mut A, mut L0, d } = self;
1093			let n = A.nrows();
1094			for j in 0..n {
1095				let x0 = &L0[j];
1096				let w0 = x0.mul_real(&d);
1097				with_dim!({
1098					let subrange_len = n - j;
1099				});
1100				{
1101					let mut A = A
1102						.rb_mut()
1103						.get_mut(j.., j)
1104						.as_row_shape_mut(subrange_len);
1105					let L0 = L0.rb().get(j..).as_row_shape(subrange_len);
1106					let simd =
1107						SimdCtx::<T, S>::new(T::simd_ctx(simd), subrange_len);
1108					let w0_conj = w0.conj();
1109					let w0_conj_neg = -w0_conj;
1110					let w0_splat = simd.splat(&w0_conj_neg);
1111					simd_iter!(for i in [simd.indices()] {
1112						let mut acc = simd.read(A.rb(), i);
1113						let l0_val = simd.read(L0, i);
1114						acc = simd.mul_add(l0_val, w0_splat, acc);
1115						simd.write(A.rb_mut(), i, acc);
1116					});
1117				}
1118				A[(j, j)] = A[(j, j)].real().to_cplx();
1119				L0[j] = w0;
1120			}
1121		}
1122	}
1123	dispatch!(Impl { A, L0, d }, Impl, T)
1124}
1125pub fn rank1_update_fallback<'a, T: ComplexField>(
1126	mut A: MatMut<'a, T>,
1127	mut L0: ColMut<'a, T>,
1128	d: T::Real,
1129) {
1130	let n = A.nrows();
1131	for j in 0..n {
1132		let x0 = &L0[j];
1133		let w0 = x0.mul_real(&d);
1134		for i in j..n {
1135			A[(i, j)] = &A[(i, j)] - &L0[i] * w0.conj();
1136		}
1137		A[(j, j)] = A[(j, j)].real().to_cplx();
1138		L0[j] = w0;
1139	}
1140}
1141/// computes the layout of required workspace for performing an $LBL^\top$
1142/// decomposition
1143pub fn cholesky_in_place_scratch<I: Index, T: ComplexField>(
1144	dim: usize,
1145	par: ParShape,
1146	params: Spec<LbltParams, T>,
1147) -> StackReq {
1148	let params = params.config;
1149	let _ = par;
1150	let mut bs = params.block_size;
1151	if bs < 2 || dim <= bs {
1152		bs = 0;
1153	}
1154	StackReq::new::<usize>(dim).and(temp_mat_scratch::<T>(dim, bs))
1155}
1156/// info about the result of the $LBL^\top$ factorization
1157#[derive(Copy, Clone, Debug)]
1158pub struct LbltInfo {
1159	/// number of pivoting transpositions
1160	pub transposition_count: usize,
1161}
1162/// computes the $LBL^\top$ factorization of $A$ and stores the factorization in
1163/// `matrix` and `subdiag`
1164///
1165/// the diagonal of the block diagonal matrix is stored on the diagonal
1166/// of `matrix`, while the subdiagonal elements of the blocks are stored in
1167/// `subdiag`
1168///
1169/// # panics
1170///
1171/// panics if the input matrix is not square
1172///
1173/// this can also panic if the provided memory in `stack` is insufficient (see
1174/// [`cholesky_in_place_scratch`]).
1175#[track_caller]
1176pub fn cholesky_in_place<'out, I: Index, T: ComplexField>(
1177	A: MatMut<'_, T>,
1178	subdiag: DiagMut<'_, T>,
1179	perm: &'out mut [I],
1180	perm_inv: &'out mut [I],
1181	par: Par,
1182	stack: &mut MemStack,
1183	params: Spec<LbltParams, T>,
1184) -> (LbltInfo, PermRef<'out, I>) {
1185	let params = params.config;
1186	let truncate = <I::Signed as SignedIndex>::truncate;
1187	let n = A.nrows();
1188	assert!(all(
1189		A.nrows() == A.ncols(),
1190		subdiag.dim() == n,
1191		perm.len() == n,
1192		perm_inv.len() == n
1193	));
1194	#[cfg(feature = "perf-warn")]
1195	if A.row_stride().unsigned_abs() != 1 && crate::__perf_warn!(CHOLESKY_WARN)
1196	{
1197		if A.col_stride().unsigned_abs() == 1 {
1198			log::warn!(
1199				target : "faer_perf",
1200				"$LBL^\top$ decomposition prefers column-major
1201			matrix. Found row-major matrix."
1202			);
1203		} else {
1204			log::warn!(
1205				target : "faer_perf",
1206				"$LBL^\top$ decomposition prefers column-major
1207			matrix. Found matrix with generic strides."
1208			);
1209		}
1210	}
1211	let (mut pivots, stack) = stack.make_with::<usize>(n, |_| 0);
1212	let pivots = &mut *pivots;
1213	let mut bs = params.block_size;
1214	if bs < 2 || n <= bs {
1215		bs = 0;
1216	}
1217	let (rook, diagonal) = match params.pivoting {
1218		PivotingStrategy::Partial => (false, false),
1219		PivotingStrategy::PartialDiag => (false, true),
1220		PivotingStrategy::Rook => (true, false),
1221		PivotingStrategy::RookDiag => (true, true),
1222		_ => (false, false),
1223	};
1224	if params.pivoting == PivotingStrategy::Full {
1225		lblt_full_piv(A, subdiag, pivots, par, params);
1226	} else {
1227		lblt_blocked(A, subdiag, pivots, bs, rook, diagonal, par, stack);
1228	}
1229	for (i, p) in perm.iter_mut().enumerate() {
1230		*p = I::from_signed(truncate(i));
1231	}
1232	let mut transposition_count = 0usize;
1233	for i in 0..n {
1234		let p = pivots[i] & !TOP_BIT;
1235		if i != p {
1236			transposition_count += 1;
1237		}
1238		perm.swap(i, p);
1239	}
1240	for (i, &p) in perm.iter().enumerate() {
1241		perm_inv[p.to_signed().zx()] = I::from_signed(truncate(i));
1242	}
1243	(
1244		LbltInfo {
1245			transposition_count,
1246		},
1247		unsafe { PermRef::new_unchecked(perm, perm_inv, n) },
1248	)
1249}