Skip to main content

faer/linalg/cholesky/bunch_kaufman/
factor.rs

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