Skip to main content

faer/sparse/linalg/
lu.rs

1//! computes the $LU$ decomposition of a given sparse matrix. see
2//! [`faer::linalg::lu`](crate::linalg::lu) for more info
3//!
4//! the entry point in this module is [`SymbolicLu`] and
5//! [`factorize_symbolic_lu`]
6//!
7//! # note
8//! the functions in this module accept unsorted inputs, and may produce
9//! unsorted decomposition factors.
10use crate::assert;
11use crate::internal_prelude_sp::*;
12use crate::sparse::utils;
13use linalg::lu::partial_pivoting::factor::PartialPivLuParams;
14use linalg_sp::cholesky::simplicial::EliminationTreeRef;
15use linalg_sp::{
16	LuError, SupernodalThreshold, SymbolicSupernodalParams, colamd,
17};
18#[inline(never)]
19fn resize_vec<T: Clone>(
20	v: &mut alloc::vec::Vec<T>,
21	n: usize,
22	exact: bool,
23	reserve_only: bool,
24	value: T,
25) -> Result<(), FaerError> {
26	let reserve = if exact {
27		alloc::vec::Vec::try_reserve_exact
28	} else {
29		alloc::vec::Vec::try_reserve
30	};
31	reserve(v, n.saturating_sub(v.len()))
32		.map_err(|_| FaerError::OutOfMemory)?;
33	if !reserve_only {
34		v.resize(Ord::max(n, v.len()), value);
35	}
36	Ok(())
37}
38/// supernodal factorization module
39///
40/// a supernodal factorization is one that processes the elements of the $LU$
41/// factors of the input matrix by blocks, rather than by single elements. this
42/// is more efficient if the lu factors are somewhat dense
43pub mod supernodal {
44	use super::*;
45	use crate::assert;
46
47	pub fn solve_in_place_scratch<I: Index, T: ComplexField>(
48		dim: usize,
49		rhs_ncols: usize,
50		par: ParShape,
51	) -> StackReq {
52		let _ = par;
53		crate::perm::permute_rows_in_place_scratch::<I, T>(dim, rhs_ncols)
54			.or(temp_mat_scratch::<T>(dim, rhs_ncols))
55	}
56
57	pub fn solve_transpose_in_place_scratch<I: Index, T: ComplexField>(
58		dim: usize,
59		rhs_ncols: usize,
60		par: ParShape,
61	) -> StackReq {
62		solve_in_place_scratch::<I, T>(dim, rhs_ncols, par)
63	}
64
65	/// $LU$ factor structure containing the symbolic structure
66	#[derive(Debug, Clone)]
67	pub struct SymbolicSupernodalLu<I: Index> {
68		pub(super) supernode_ptr: alloc::vec::Vec<I>,
69		pub(super) super_etree: alloc::vec::Vec<I>,
70		pub(super) nrows: usize,
71		pub(super) ncols: usize,
72	}
73	/// $LU$ factor structure containing the symbolic and numerical
74	/// representations
75	#[derive(Debug, Clone)]
76	pub struct SupernodalLu<I: Index, T> {
77		nrows: usize,
78		ncols: usize,
79		nsupernodes: usize,
80		supernode_ptr: alloc::vec::Vec<I>,
81		l_col_ptr_for_row_idx: alloc::vec::Vec<I>,
82		l_col_ptr_for_val: alloc::vec::Vec<I>,
83		l_row_idx: alloc::vec::Vec<I>,
84		l_val: alloc::vec::Vec<T>,
85		ut_col_ptr_for_row_idx: alloc::vec::Vec<I>,
86		ut_col_ptr_for_val: alloc::vec::Vec<I>,
87		ut_row_idx: alloc::vec::Vec<I>,
88		ut_val: alloc::vec::Vec<T>,
89
90		u_compress_cols: alloc::vec::Vec<alloc::vec::Vec<I>>,
91		l_compress_rows: alloc::vec::Vec<alloc::vec::Vec<I>>,
92	}
93	impl<I: Index, T> Default for SupernodalLu<I, T> {
94		fn default() -> Self {
95			Self::new()
96		}
97	}
98	impl<I: Index, T> SupernodalLu<I, T> {
99		/// creates a new supernodal $LU$ of a $0 \times 0$ matrix
100		#[inline]
101		pub fn new() -> Self {
102			Self {
103				nrows: 0,
104				ncols: 0,
105				nsupernodes: 0,
106				supernode_ptr: alloc::vec::Vec::new(),
107				l_col_ptr_for_row_idx: alloc::vec::Vec::new(),
108				ut_col_ptr_for_row_idx: alloc::vec::Vec::new(),
109				l_col_ptr_for_val: alloc::vec::Vec::new(),
110				ut_col_ptr_for_val: alloc::vec::Vec::new(),
111				l_row_idx: alloc::vec::Vec::new(),
112				ut_row_idx: alloc::vec::Vec::new(),
113				l_val: alloc::vec::Vec::new(),
114				ut_val: alloc::vec::Vec::new(),
115				u_compress_cols: alloc::vec::Vec::new(),
116				l_compress_rows: alloc::vec::Vec::new(),
117			}
118		}
119
120		/// returns the number of rows of $A$
121		#[inline]
122		pub fn nrows(&self) -> usize {
123			self.nrows
124		}
125
126		/// returns the number of columns of $A$
127		#[inline]
128		pub fn ncols(&self) -> usize {
129			self.ncols
130		}
131
132		/// returns the number of supernodes
133		#[inline]
134		pub fn n_supernodes(&self) -> usize {
135			self.nsupernodes
136		}
137
138		/// solves the equation $A x = \text{rhs}$ and stores the result in
139		/// `rhs`, implicitly conjugating $A$ if needed
140		///
141		/// # panics
142		/// - panics if `self.nrows() != self.ncols()`
143		/// - panics if `rhs.nrows() != self.nrows()`
144		#[track_caller]
145		pub fn solve_in_place_with_conj(
146			&self,
147			row_perm: PermRef<'_, I>,
148			col_perm: PermRef<'_, I>,
149			conj_lhs: Conj,
150			rhs: MatMut<'_, T>,
151			mut par: Par,
152			stack: &mut MemStack,
153		) where
154			T: ComplexField,
155		{
156			assert!(all(
157				self.nrows() == self.ncols(),
158				self.nrows() == rhs.nrows()
159			));
160			let mut X = rhs;
161			crate::perm::permute_rows_in_place(X.rb_mut(), row_perm, stack);
162			self.l_solve_in_place_with_conj(conj_lhs, X.rb_mut(), par.reborrow(), stack);
163			self.u_solve_in_place_with_conj(conj_lhs, X.rb_mut(), par, stack);
164			crate::perm::permute_rows_in_place(
165				X.rb_mut(),
166				col_perm.inverse(),
167				stack,
168			);
169		}
170
171		/// solves the equation $A^\top x = \text{rhs}$ and stores the result in
172		/// `rhs`, implicitly conjugating $A$ if needed
173		///
174		/// # panics
175		/// - panics if `self.nrows() != self.ncols()`
176		/// - panics if `rhs.nrows() != self.nrows()`
177		#[track_caller]
178		pub fn solve_transpose_in_place_with_conj(
179			&self,
180			row_perm: PermRef<'_, I>,
181			col_perm: PermRef<'_, I>,
182			conj_lhs: Conj,
183			rhs: MatMut<'_, T>,
184			mut par: Par,
185			stack: &mut MemStack,
186		) where
187			T: ComplexField,
188		{
189			assert!(all(
190				self.nrows() == self.ncols(),
191				self.nrows() == rhs.nrows()
192			));
193			let mut X = rhs;
194			crate::perm::permute_rows_in_place(X.rb_mut(), col_perm, stack);
195			self.u_solve_transpose_in_place_with_conj(
196				conj_lhs,
197				X.rb_mut(),
198				par.reborrow(),
199				stack,
200			);
201			self.l_solve_transpose_in_place_with_conj(
202				conj_lhs,
203				X.rb_mut(),
204				par,
205				stack,
206			);
207			crate::perm::permute_rows_in_place(
208				X.rb_mut(),
209				row_perm.inverse(),
210				stack,
211			);
212		}
213
214		#[track_caller]
215		pub(crate) fn l_solve_in_place_with_conj(
216			&self,
217			conj_lhs: Conj,
218			rhs: MatMut<'_, T>,
219			mut par: Par,
220			stack: &mut MemStack,
221		) where
222			T: ComplexField,
223		{
224			let lu = self;
225			assert!(lu.nrows() == lu.ncols());
226			assert!(lu.nrows() == rhs.nrows());
227			let mut X = rhs;
228			let nrhs = X.ncols();
229			let supernode_ptr = &*lu.supernode_ptr;
230			alloca!('stack: {
231				let mut work =
232					unsafe { mat![uninit::<T>, X.nrows(), X.ncols()] };
233			});
234			for s in 0..lu.nsupernodes {
235				let s_begin = supernode_ptr[s].zx();
236				let s_end = supernode_ptr[s + 1].zx();
237				let s_size = s_end - s_begin;
238				let s_row_idx_count = lu.l_col_ptr_for_row_idx[s + 1].zx()
239					- lu.l_col_ptr_for_row_idx[s].zx();
240				let L = &lu.l_val[lu.l_col_ptr_for_val[s].zx()
241					..lu.l_col_ptr_for_val[s + 1].zx()];
242				let L =
243					MatRef::from_column_major_slice(L, s_row_idx_count, s_size);
244				let (L_top, L_bot) = L.split_at_row(s_size);
245				linalg::triangular_solve::solve_unit_lower_triangular_in_place_with_conj(
246					L_top,
247					conj_lhs,
248					X.rb_mut().subrows_mut(s_begin, s_size),
249					par.reborrow(),
250				);
251				linalg::matmul::matmul_with_conj(
252					work.rb_mut().subrows_mut(0, s_row_idx_count - s_size),
253					Accum::Replace,
254					L_bot,
255					conj_lhs,
256					X.rb().subrows(s_begin, s_size),
257					Conj::No,
258					one::<T>(),
259					par.reborrow(),
260				);
261				for j in 0..nrhs {
262					for (idx, &i) in lu.l_row_idx[lu.l_col_ptr_for_row_idx[s]
263						.zx()
264						..lu.l_col_ptr_for_row_idx[s + 1].zx()][s_size..]
265						.iter()
266						.enumerate()
267					{
268						let i = i.zx();
269						X[(i, j)] -= &work[(idx, j)];
270					}
271				}
272			}
273		}
274
275		#[track_caller]
276		pub(crate) fn l_solve_transpose_in_place_with_conj(
277			&self,
278			conj_lhs: Conj,
279			rhs: MatMut<'_, T>,
280			mut par: Par,
281			stack: &mut MemStack,
282		) where
283			T: ComplexField,
284		{
285			let lu = self;
286			assert!(lu.nrows() == lu.ncols());
287			assert!(lu.nrows() == rhs.nrows());
288			let mut X = rhs;
289			let nrhs = X.ncols();
290			let supernode_ptr = &*lu.supernode_ptr;
291			alloca!('stack: {
292				let mut work =
293					unsafe { mat![uninit::<T>, X.nrows(), X.ncols()] };
294			});
295			for s in (0..lu.nsupernodes).rev() {
296				let s_begin = supernode_ptr[s].zx();
297				let s_end = supernode_ptr[s + 1].zx();
298				let s_size = s_end - s_begin;
299				let s_row_idx_count = lu.l_col_ptr_for_row_idx[s + 1].zx()
300					- lu.l_col_ptr_for_row_idx[s].zx();
301				let L = &lu.l_val[lu.l_col_ptr_for_val[s].zx()
302					..lu.l_col_ptr_for_val[s + 1].zx()];
303				let L =
304					MatRef::from_column_major_slice(L, s_row_idx_count, s_size);
305				let (L_top, L_bot) = L.split_at_row(s_size);
306				for j in 0..nrhs {
307					for (idx, &i) in lu.l_row_idx[lu.l_col_ptr_for_row_idx[s]
308						.zx()
309						..lu.l_col_ptr_for_row_idx[s + 1].zx()][s_size..]
310						.iter()
311						.enumerate()
312					{
313						let i = i.zx();
314						work[(idx, j)] = X[(i, j)].copy();
315					}
316				}
317				linalg::matmul::matmul_with_conj(
318					X.rb_mut().subrows_mut(s_begin, s_size),
319					Accum::Add,
320					L_bot.transpose(),
321					conj_lhs,
322					work.rb().subrows(0, s_row_idx_count - s_size),
323					Conj::No,
324					-one::<T>(),
325					par.reborrow(),
326				);
327				linalg::triangular_solve::solve_unit_upper_triangular_in_place_with_conj(
328					L_top.transpose(),
329					conj_lhs,
330					X.rb_mut().subrows_mut(s_begin, s_size),
331					par.reborrow(),
332				);
333			}
334		}
335
336		#[track_caller]
337		pub(crate) fn u_solve_in_place_with_conj(
338			&self,
339			conj_lhs: Conj,
340			rhs: MatMut<'_, T>,
341			mut par: Par,
342			stack: &mut MemStack,
343		) where
344			T: ComplexField,
345		{
346			let lu = self;
347			assert!(lu.nrows() == lu.ncols());
348			assert!(lu.nrows() == rhs.nrows());
349			let mut X = rhs;
350			let nrhs = X.ncols();
351			let supernode_ptr = &*lu.supernode_ptr;
352			alloca!('stack: {
353				let mut work =
354					unsafe { mat![uninit::<T>, X.nrows(), X.ncols()] };
355			});
356			for s in (0..lu.nsupernodes).rev() {
357				let s_begin = supernode_ptr[s].zx();
358				let s_end = supernode_ptr[s + 1].zx();
359				let s_size = s_end - s_begin;
360				let s_row_idx_count = lu.l_col_ptr_for_row_idx[s + 1].zx()
361					- lu.l_col_ptr_for_row_idx[s].zx();
362				let s_col_index_count = lu.ut_col_ptr_for_row_idx[s + 1].zx()
363					- lu.ut_col_ptr_for_row_idx[s].zx();
364				let L = &lu.l_val[lu.l_col_ptr_for_val[s].zx()
365					..lu.l_col_ptr_for_val[s + 1].zx()];
366				let L =
367					MatRef::from_column_major_slice(L, s_row_idx_count, s_size);
368				let U = &lu.ut_val[lu.ut_col_ptr_for_val[s].zx()
369					..lu.ut_col_ptr_for_val[s + 1].zx()];
370				let U_right = MatRef::from_column_major_slice(
371					U,
372					s_col_index_count,
373					s_size,
374				)
375				.transpose();
376				for j in 0..nrhs {
377					for (idx, &i) in lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[s]
378						.zx()
379						..lu.ut_col_ptr_for_row_idx[s + 1].zx()]
380						.iter()
381						.enumerate()
382					{
383						let i = i.zx();
384						work[(idx, j)] = X[(i, j)].copy();
385					}
386				}
387				let (U_left, _) = L.split_at_row(s_size);
388				linalg::matmul::matmul_with_conj(
389					X.rb_mut().subrows_mut(s_begin, s_size),
390					Accum::Add,
391					U_right,
392					conj_lhs,
393					work.rb().subrows(0, s_col_index_count),
394					Conj::No,
395					-one::<T>(),
396					par.reborrow(),
397				);
398				linalg::triangular_solve::solve_upper_triangular_in_place_with_conj(U_left, conj_lhs, X.rb_mut().subrows_mut(s_begin, s_size), par.reborrow());
399			}
400		}
401
402		#[track_caller]
403		pub(crate) fn u_solve_transpose_in_place_with_conj(
404			&self,
405			conj_lhs: Conj,
406			rhs: MatMut<'_, T>,
407			mut par: Par,
408			stack: &mut MemStack,
409		) where
410			T: ComplexField,
411		{
412			let lu = self;
413			assert!(lu.nrows() == lu.ncols());
414			assert!(lu.nrows() == rhs.nrows());
415			let mut X = rhs;
416			let nrhs = X.ncols();
417			let supernode_ptr = &*lu.supernode_ptr;
418			alloca!('stack: {
419				let mut work =
420					unsafe { mat![uninit::<T>, X.nrows(), X.ncols()] };
421			});
422			for s in 0..lu.nsupernodes {
423				let s_begin = supernode_ptr[s].zx();
424				let s_end = supernode_ptr[s + 1].zx();
425				let s_size = s_end - s_begin;
426				let s_row_idx_count = lu.l_col_ptr_for_row_idx[s + 1].zx()
427					- lu.l_col_ptr_for_row_idx[s].zx();
428				let s_col_index_count = lu.ut_col_ptr_for_row_idx[s + 1].zx()
429					- lu.ut_col_ptr_for_row_idx[s].zx();
430				let L = &lu.l_val[lu.l_col_ptr_for_val[s].zx()
431					..lu.l_col_ptr_for_val[s + 1].zx()];
432				let L =
433					MatRef::from_column_major_slice(L, s_row_idx_count, s_size);
434				let U = &lu.ut_val[lu.ut_col_ptr_for_val[s].zx()
435					..lu.ut_col_ptr_for_val[s + 1].zx()];
436				let U_right = MatRef::from_column_major_slice(
437					U,
438					s_col_index_count,
439					s_size,
440				)
441				.transpose();
442				let (U_left, _) = L.split_at_row(s_size);
443				linalg::triangular_solve::solve_lower_triangular_in_place_with_conj(
444					U_left.transpose(),
445					conj_lhs,
446					X.rb_mut().subrows_mut(s_begin, s_size),
447					par.reborrow(),
448				);
449				linalg::matmul::matmul_with_conj(
450					work.rb_mut().subrows_mut(0, s_col_index_count),
451					Accum::Replace,
452					U_right.transpose(),
453					conj_lhs,
454					X.rb().subrows(s_begin, s_size),
455					Conj::No,
456					one::<T>(),
457					par.reborrow(),
458				);
459				for j in 0..nrhs {
460					for (idx, &i) in lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[s]
461						.zx()
462						..lu.ut_col_ptr_for_row_idx[s + 1].zx()]
463						.iter()
464						.enumerate()
465					{
466						let i = i.zx();
467						X[(i, j)] -= &work[(idx, j)];
468					}
469				}
470			}
471		}
472	}
473	/// computes the layout of the workspace required to compute the symbolic
474	/// $LU$ factorization of a square matrix with size `n`.
475	pub fn factorize_supernodal_symbolic_lu_scratch<I: Index>(
476		nrows: usize,
477		ncols: usize,
478	) -> StackReq {
479		let _ = nrows;
480		linalg_sp::cholesky::supernodal::factorize_supernodal_symbolic_cholesky_scratch::<I>(ncols)
481	}
482	/// computes the symbolic structure of the $LU$ factors of the matrix $A$
483	#[track_caller]
484	pub fn factorize_supernodal_symbolic_lu<I: Index>(
485		A: SymbolicSparseColMatRef<'_, I>,
486		col_perm: Option<PermRef<'_, I>>,
487		min_col: &[I],
488		etree: EliminationTreeRef<'_, I>,
489		col_counts: &[I],
490		stack: &mut MemStack,
491		params: SymbolicSupernodalParams<'_>,
492	) -> Result<SymbolicSupernodalLu<I>, FaerError> {
493		let m = A.nrows();
494		let n = A.ncols();
495		with_dim!(M, m);
496		with_dim!(N, n);
497		let I = I::truncate;
498		let A = A.as_shape(M, N);
499		let min_col = Array::from_ref(
500			MaybeIdx::from_slice_ref_checked(bytemuck::cast_slice(min_col), N),
501			M,
502		);
503		let etree = etree.as_bound(N);
504		let L = linalg_sp::cholesky::supernodal::ghost_factorize_supernodal_symbolic(
505			A,
506			col_perm.map(|perm| perm.as_shape(N)),
507			Some(min_col),
508			linalg_sp::cholesky::supernodal::CholeskyInput::ATA,
509			etree,
510			Array::from_ref(col_counts, N),
511			stack,
512			params,
513		)?;
514		let n_supernodes = L.n_supernodes();
515		let mut super_etree = try_zeroed::<I>(n_supernodes)?;
516		let (index_to_super, _) = unsafe { stack.make_raw::<I>(*N) };
517		for s in 0..n_supernodes {
518			index_to_super
519				[L.supernode_begin[s].zx()..L.supernode_begin[s + 1].zx()]
520				.fill(I(s));
521		}
522		for s in 0..n_supernodes {
523			let last = L.supernode_begin[s + 1].zx() - 1;
524			if let Some(parent) = etree[N.check(last)].idx() {
525				super_etree[s] = index_to_super[*parent.zx()];
526			} else {
527				super_etree[s] = I(NONE);
528			}
529		}
530		Ok(SymbolicSupernodalLu {
531			supernode_ptr: L.supernode_begin,
532			super_etree,
533			nrows: *A.nrows(),
534			ncols: *A.ncols(),
535			// row_major: L.row_major,
536			// supernode_postorder: L.supernode_postorder,
537			// supernode_postorder_inv: L.supernode_postorder_inv,
538			// descendant_count: L.descendant_count,
539		})
540	}
541	struct MatU8 {
542		data: alloc::vec::Vec<u8>,
543		nrows: usize,
544	}
545	impl MatU8 {
546		fn new() -> Self {
547			Self {
548				data: alloc::vec::Vec::new(),
549				nrows: 0,
550			}
551		}
552
553		fn with_dims(nrows: usize, ncols: usize) -> Result<Self, FaerError> {
554			Ok(Self {
555				data: try_collect((0..(nrows * ncols)).map(|_| 1u8))?,
556				nrows,
557			})
558		}
559	}
560	impl core::ops::Index<(usize, usize)> for MatU8 {
561		type Output = u8;
562
563		#[inline(always)]
564		fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
565			&self.data[row + col * self.nrows]
566		}
567	}
568	impl core::ops::IndexMut<(usize, usize)> for MatU8 {
569		#[inline(always)]
570		fn index_mut(
571			&mut self,
572			(row, col): (usize, usize),
573		) -> &mut Self::Output {
574			&mut self.data[row + col * self.nrows]
575		}
576	}
577	struct Front;
578	struct LPanel;
579	struct UPanel;
580	#[inline(never)]
581	fn noinline<T, R>(_: T, f: impl FnOnce() -> R) -> R {
582		f()
583	}
584	/// computes the layout of the workspace required to perform a numeric $LU$
585	/// factorization
586	pub fn factorize_supernodal_numeric_lu_scratch<
587		I: Index,
588		T: ComplexField,
589	>(
590		symbolic: &SymbolicSupernodalLu<I>,
591		params: Spec<PartialPivLuParams, T>,
592	) -> StackReq {
593		let m = StackReq::new::<I>(symbolic.nrows);
594		let n = StackReq::new::<I>(symbolic.ncols);
595		_ = params;
596		StackReq::all_of(&[
597			n.array(2),
598			m.array(5),
599			StackReq::new::<I>(symbolic.supernode_ptr.len()),
600		])
601	}
602	/// computes the numeric values of the $LU$ factors of the matrix $A$ as
603	/// well as the row pivoting permutation, and stores them in `lu` and
604	/// `row_perm`/`row_perm_inv`
605	pub fn factorize_supernodal_numeric_lu<I: Index, T: ComplexField>(
606		row_perm: &mut [I],
607		row_perm_inv: &mut [I],
608		lu: &mut SupernodalLu<I, T>,
609		A: SparseColMatRef<'_, I, T>,
610		AT: SparseColMatRef<'_, I, T>,
611		col_perm: PermRef<'_, I>,
612		symbolic: &SymbolicSupernodalLu<I>,
613		mut par: Par,
614		stack: &mut MemStack,
615		params: Spec<PartialPivLuParams, T>,
616	) -> Result<(), LuError> {
617		use linalg_sp::cholesky::supernodal::partition_fn;
618		let SymbolicSupernodalLu {
619			supernode_ptr,
620			super_etree,
621			nrows: _,
622			ncols: _,
623			..
624		} = symbolic;
625
626		let I = I::truncate;
627		let I_checked = |x: usize| -> Result<I, FaerError> {
628			if x > I::Signed::MAX.zx() {
629				Err(FaerError::IndexOverflow)
630			} else {
631				Ok(I(x))
632			}
633		};
634		let to_wide = |x: I| -> u128 { x.zx() as _ };
635		let from_wide_checked = |x: u128| -> Result<I, FaerError> {
636			if x > I::Signed::MAX.zx() as u128 {
637				Err(FaerError::IndexOverflow)
638			} else {
639				Ok(I(x as _))
640			}
641		};
642		let m = A.nrows();
643		let n = A.ncols();
644		assert!(m >= n);
645		assert!(all(AT.nrows() == n, AT.ncols() == m));
646		assert!(all(row_perm.len() == m, row_perm_inv.len() == m));
647		let n_supernodes = super_etree.len();
648		assert!(supernode_ptr.len() == n_supernodes + 1);
649		assert!(supernode_ptr[n_supernodes].zx() == n);
650		lu.nrows = 0;
651		lu.ncols = 0;
652		lu.nsupernodes = 0;
653		lu.supernode_ptr.clear();
654
655		let (index_to_super, stack) = unsafe { stack.make_raw::<I>(n) };
656		let (supernode_visited, stack) =
657			unsafe { stack.make_raw::<I>(n_supernodes) };
658		let u_compress_cols = &mut lu.u_compress_cols;
659		let l_compress_rows = &mut lu.l_compress_rows;
660
661		supernode_visited.fill(I(0));
662
663		u_compress_cols
664			.try_reserve_exact(n_supernodes)
665			.map_err(|_| FaerError::OutOfMemory)?;
666		l_compress_rows
667			.try_reserve_exact(m)
668			.map_err(|_| FaerError::OutOfMemory)?;
669
670		for v in u_compress_cols.iter_mut() {
671			v.clear();
672		}
673		for v in l_compress_rows.iter_mut() {
674			v.clear();
675		}
676
677		u_compress_cols.resize_with(n_supernodes, alloc::vec::Vec::new);
678		l_compress_rows.resize_with(m, alloc::vec::Vec::new);
679
680		for s in 0..n_supernodes {
681			index_to_super[supernode_ptr[s].zx()..supernode_ptr[s + 1].zx()]
682				.fill(I(s));
683		}
684
685		let (col_global_to_local, stack) = unsafe { stack.make_raw::<I>(n) };
686		let (row_global_to_local, stack) = unsafe { stack.make_raw::<I>(m) };
687		let (marked, stack) = unsafe { stack.make_raw::<I>(m) };
688		let (indices, stack) = unsafe { stack.make_raw::<I>(m) };
689		let (transpositions, stack) = unsafe { stack.make_raw::<I>(m) };
690		let (d_active_rows, _) = unsafe { stack.make_raw::<I>(m) };
691		col_global_to_local.fill(I(NONE));
692		row_global_to_local.fill(I(NONE));
693		marked.fill(I(0));
694		resize_vec(
695			&mut lu.l_col_ptr_for_row_idx,
696			n_supernodes + 1,
697			true,
698			false,
699			I(0),
700		)?;
701		resize_vec(
702			&mut lu.ut_col_ptr_for_row_idx,
703			n_supernodes + 1,
704			true,
705			false,
706			I(0),
707		)?;
708		resize_vec(
709			&mut lu.l_col_ptr_for_val,
710			n_supernodes + 1,
711			true,
712			false,
713			I(0),
714		)?;
715		resize_vec(
716			&mut lu.ut_col_ptr_for_val,
717			n_supernodes + 1,
718			true,
719			false,
720			I(0),
721		)?;
722		lu.l_col_ptr_for_row_idx[0] = I(0);
723		lu.ut_col_ptr_for_row_idx[0] = I(0);
724		lu.l_col_ptr_for_val[0] = I(0);
725		lu.ut_col_ptr_for_val[0] = I(0);
726		for i in 0..m {
727			row_perm[i] = I(i);
728		}
729		for i in 0..m {
730			row_perm_inv[i] = I(i);
731		}
732		let (col_perm, col_perm_inv) = col_perm.arrays();
733		let mut contrib_work = try_collect((0..n_supernodes).map(|_| {
734			(
735				alloc::vec::Vec::<T>::new(),
736				alloc::vec::Vec::<I>::new(),
737				0usize,
738				MatU8::new(),
739			)
740		}))?;
741		let work_to_mat_mut =
742			|v: &mut alloc::vec::Vec<T>, nrows: usize, ncols: usize| unsafe {
743				MatMut::from_raw_parts_mut(
744					v.as_mut_ptr(),
745					nrows,
746					ncols,
747					1,
748					nrows as isize,
749				)
750			};
751		let mut A_leftover = A.compute_nnz();
752		for s in 0..n_supernodes {
753			let s_begin = supernode_ptr[s].zx();
754			let s_end = supernode_ptr[s + 1].zx();
755			let s_size = s_end - s_begin;
756			let mut s_row_idx_count = 0usize;
757			let (left_contrib, right_contrib) = contrib_work.split_at_mut(s);
758			let s_row_idx = &mut *indices;
759			for j in s_begin..s_end {
760				let pj = col_perm[j].zx();
761				let row_idx = A.row_idx_of_col_raw(pj);
762				for i in row_idx {
763					let i = i.zx();
764					let pi = row_perm_inv[i].zx();
765					if pi < s_begin {
766						continue;
767					}
768					if marked[i] < I(2 * s + 1) {
769						s_row_idx[s_row_idx_count] = I(i);
770						s_row_idx_count += 1;
771						marked[i] = I(2 * s + 1);
772					}
773				}
774			}
775
776			for d in &u_compress_cols[s] {
777				let d = d.zx();
778				let d_begin = supernode_ptr[d].zx();
779				let d_end = supernode_ptr[d + 1].zx();
780				let d_size = d_end - d_begin;
781				let d_row_idx = &lu.l_row_idx[lu.l_col_ptr_for_row_idx[d].zx()
782					..lu.l_col_ptr_for_row_idx[d + 1].zx()][d_size..];
783				let d_col_idx = &lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[d].zx()
784					..lu.ut_col_ptr_for_row_idx[d + 1].zx()];
785				let d_col_start =
786					d_col_idx.partition_point(partition_fn(s_begin));
787				if d_col_start < d_col_idx.len()
788					&& d_col_idx[d_col_start].zx() < s_end
789				{
790					for i in d_row_idx.iter() {
791						let i = i.zx();
792						let pi = row_perm_inv[i].zx();
793						if pi < s_begin {
794							continue;
795						}
796						if marked[i] < I(2 * s + 1) {
797							s_row_idx[s_row_idx_count] = I(i);
798							s_row_idx_count += 1;
799							marked[i] = I(2 * s + 1);
800						}
801					}
802				}
803			}
804			lu.l_col_ptr_for_row_idx[s + 1] =
805				I_checked(lu.l_col_ptr_for_row_idx[s].zx() + s_row_idx_count)?;
806			lu.l_col_ptr_for_val[s + 1] = from_wide_checked(
807				to_wide(lu.l_col_ptr_for_val[s])
808					+ (s_row_idx_count as u128 * s_size as u128),
809			)?;
810			resize_vec(
811				&mut lu.l_row_idx,
812				lu.l_col_ptr_for_row_idx[s + 1].zx(),
813				false,
814				false,
815				I(0),
816			)?;
817			resize_vec::<T>(
818				&mut lu.l_val,
819				lu.l_col_ptr_for_val[s + 1].zx(),
820				false,
821				false,
822				zero::<T>(),
823			)?;
824			lu.l_row_idx[lu.l_col_ptr_for_row_idx[s].zx()
825				..lu.l_col_ptr_for_row_idx[s + 1].zx()]
826				.copy_from_slice(&s_row_idx[..s_row_idx_count]);
827			lu.l_row_idx[lu.l_col_ptr_for_row_idx[s].zx()
828				..lu.l_col_ptr_for_row_idx[s + 1].zx()]
829				.sort_unstable();
830			let (left_row_idx, right_row_idx) =
831				lu.l_row_idx.split_at_mut(lu.l_col_ptr_for_row_idx[s].zx());
832			let s_row_idx =
833				&mut right_row_idx[0..lu.l_col_ptr_for_row_idx[s + 1].zx()
834					- lu.l_col_ptr_for_row_idx[s].zx()];
835			for (idx, i) in s_row_idx.iter().enumerate() {
836				let i = i.zx();
837				row_global_to_local[i] = I(idx);
838			}
839			let s_L = &mut lu.l_val[lu.l_col_ptr_for_val[s].zx()
840				..lu.l_col_ptr_for_val[s + 1].zx()];
841			let mut s_L = MatMut::from_column_major_slice_mut(
842				s_L,
843				s_row_idx_count,
844				s_size,
845			);
846			s_L.fill(zero());
847			for j in s_begin..s_end {
848				let pj = col_perm[j].zx();
849				let row_idx = A.row_idx_of_col(pj);
850				let val = A.val_of_col(pj);
851				for (i, val) in iter::zip(row_idx, val) {
852					let pi = row_perm_inv[i].zx();
853					if pi < s_begin {
854						continue;
855					}
856					assert!(A_leftover > 0);
857					A_leftover -= 1;
858					let ix = row_global_to_local[i].zx();
859					let iy = j - s_begin;
860					s_L[(ix, iy)] += val;
861				}
862			}
863			noinline(LPanel, || {
864				for d in &u_compress_cols[s] {
865					let d = d.zx();
866					if left_contrib[d].0.is_empty() {
867						continue;
868					}
869					let d_begin = supernode_ptr[d].zx();
870					let d_end = supernode_ptr[d + 1].zx();
871					let d_size = d_end - d_begin;
872					let d_row_idx = &left_row_idx[lu.l_col_ptr_for_row_idx[d]
873						.zx()
874						..lu.l_col_ptr_for_row_idx[d + 1].zx()][d_size..];
875					let d_col_idx = &lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[d]
876						.zx()
877						..lu.ut_col_ptr_for_row_idx[d + 1].zx()];
878					let d_col_start =
879						d_col_idx.partition_point(partition_fn(s_begin));
880					if d_col_start < d_col_idx.len()
881						&& d_col_idx[d_col_start].zx() < s_end
882					{
883						let d_col_mid = d_col_start
884							+ d_col_idx[d_col_start..]
885								.partition_point(partition_fn(s_end));
886						let mut d_LU_cols = work_to_mat_mut(
887							&mut left_contrib[d].0,
888							d_row_idx.len(),
889							d_col_idx.len(),
890						)
891						.subcols_mut(d_col_start, d_col_mid - d_col_start);
892						let left_contrib = &mut left_contrib[d];
893						let d_active = &mut left_contrib.1[d_col_start..];
894						let d_active_count = &mut left_contrib.2;
895						let d_active_mat = &mut left_contrib.3;
896						for (d_j, j) in
897							d_col_idx[d_col_start..d_col_mid].iter().enumerate()
898						{
899							if d_active[d_j] > I(0) {
900								let mut taken_rows = 0usize;
901								let j = j.zx();
902								let s_j = j - s_begin;
903								for (d_i, i) in d_row_idx.iter().enumerate() {
904									let i = i.zx();
905									let pi = row_perm_inv[i].zx();
906									if pi < s_begin {
907										continue;
908									}
909									let s_i = row_global_to_local[i].zx();
910									s_L[(s_i, s_j)] -= &d_LU_cols[(d_i, d_j)];
911									d_LU_cols[(d_i, d_j)] = zero::<T>();
912									taken_rows += d_active_mat
913										[(d_i, d_j + d_col_start)]
914										as usize;
915									d_active_mat[(d_i, d_j + d_col_start)] = 0;
916								}
917								assert!(d_active[d_j] >= I(taken_rows));
918								d_active[d_j] -= I(taken_rows);
919								if d_active[d_j] == I(0) {
920									assert!(*d_active_count > 0);
921									*d_active_count -= 1;
922								}
923							}
924						}
925						if *d_active_count == 0 {
926							left_contrib.0.clear();
927							left_contrib.1 = alloc::vec::Vec::new();
928							left_contrib.2 = 0;
929							left_contrib.3 = MatU8::new();
930						}
931					}
932				}
933			});
934			if s_L.nrows() < s_L.ncols() {
935				return Err(LuError::SymbolicSingular {
936					index: s_begin + s_L.nrows(),
937				});
938			}
939			let transpositions = &mut transpositions[s_begin..s_end];
940			crate::linalg::lu::partial_pivoting::factor::lu_in_place_recursion(
941				s_L.rb_mut(),
942				0,
943				s_size,
944				transpositions,
945				par.reborrow(),
946				params,
947			);
948			for i in 0..s_size {
949				if s_L[(i, i)].abs1() == zero::<T::Real>() {
950					return Err(LuError::NumericSingular {
951						index: s_begin + i,
952					});
953				}
954			}
955			for (idx, t) in transpositions.iter().enumerate() {
956				let i_t = s_row_idx[idx + t.zx()].zx();
957				let kk = row_perm_inv[i_t].zx();
958				row_perm.swap(s_begin + idx, kk);
959				row_perm_inv
960					.swap(row_perm[s_begin + idx].zx(), row_perm[kk].zx());
961				s_row_idx.swap(idx, idx + t.zx());
962			}
963			for (idx, t) in transpositions.iter().enumerate().rev() {
964				row_global_to_local
965					.swap(s_row_idx[idx].zx(), s_row_idx[idx + t.zx()].zx());
966			}
967			for i in s_row_idx.iter() {
968				let i = i.zx();
969				let pi = row_perm_inv[i].zx();
970
971				if pi >= s_end {
972					l_compress_rows
973						.try_reserve(1)
974						.ok()
975						.ok_or(FaerError::OutOfMemory)?;
976					l_compress_rows[i].push(I(s));
977				}
978			}
979			for (idx, i) in s_row_idx.iter().enumerate() {
980				assert!(row_global_to_local[i.zx()] == I(idx));
981			}
982			let s_col_idx = &mut indices[..n];
983			let mut s_col_index_count = 0usize;
984			for i in s_begin..s_end {
985				let pi = row_perm[i].zx();
986				for j in AT.row_idx_of_col(pi) {
987					let pj = col_perm_inv[j].zx();
988					if pj < s_end {
989						continue;
990					}
991					if marked[pj] < I(2 * s + 2) {
992						s_col_idx[s_col_index_count] = I(pj);
993						s_col_index_count += 1;
994						marked[pj] = I(2 * s + 2);
995					}
996				}
997			}
998
999			for i in s_begin..s_end {
1000				for d in &l_compress_rows[row_perm[i].zx()] {
1001					let d = d.zx();
1002					if supernode_visited[d].zx() < 2 * s + 1 {
1003						supernode_visited[d] = I(2 * s + 1);
1004
1005						if !left_contrib[d].0.is_empty() {
1006							let d_col_idx = &lu.ut_row_idx[lu
1007								.ut_col_ptr_for_row_idx[d]
1008								.zx()
1009								..lu.ut_col_ptr_for_row_idx[d + 1].zx()];
1010							let d_col_start =
1011								d_col_idx.partition_point(partition_fn(s_end));
1012							for j in &d_col_idx[d_col_start..] {
1013								let j = j.zx();
1014								if marked[j] < I(2 * s + 2) {
1015									s_col_idx[s_col_index_count] = I(j);
1016									s_col_index_count += 1;
1017									marked[j] = I(2 * s + 2);
1018								}
1019							}
1020						}
1021					}
1022				}
1023			}
1024
1025			lu.ut_col_ptr_for_row_idx[s + 1] = I_checked(
1026				lu.ut_col_ptr_for_row_idx[s].zx() + s_col_index_count,
1027			)?;
1028			lu.ut_col_ptr_for_val[s + 1] = from_wide_checked(
1029				to_wide(lu.ut_col_ptr_for_val[s])
1030					+ (s_col_index_count as u128 * s_size as u128),
1031			)?;
1032			resize_vec(
1033				&mut lu.ut_row_idx,
1034				lu.ut_col_ptr_for_row_idx[s + 1].zx(),
1035				false,
1036				false,
1037				I(0),
1038			)?;
1039			resize_vec::<T>(
1040				&mut lu.ut_val,
1041				lu.ut_col_ptr_for_val[s + 1].zx(),
1042				false,
1043				false,
1044				zero::<T>(),
1045			)?;
1046			lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[s].zx()
1047				..lu.ut_col_ptr_for_row_idx[s + 1].zx()]
1048				.copy_from_slice(&s_col_idx[..s_col_index_count]);
1049			lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[s].zx()
1050				..lu.ut_col_ptr_for_row_idx[s + 1].zx()]
1051				.sort_unstable();
1052			let s_col_idx = &lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[s].zx()
1053				..lu.ut_col_ptr_for_row_idx[s + 1].zx()];
1054			for (idx, j) in s_col_idx.iter().enumerate() {
1055				let j = j.zx();
1056				col_global_to_local[j] = I(idx);
1057
1058				let j_node = index_to_super[j].zx();
1059				if j_node > s
1060					&& u_compress_cols[j_node].last().copied() != Some(I(s))
1061				{
1062					u_compress_cols
1063						.try_reserve(1)
1064						.ok()
1065						.ok_or(FaerError::OutOfMemory)?;
1066					u_compress_cols[j_node].push(I(s));
1067				}
1068			}
1069			let s_U = &mut lu.ut_val[lu.ut_col_ptr_for_val[s].zx()
1070				..lu.ut_col_ptr_for_val[s + 1].zx()];
1071			let mut s_U = MatMut::from_column_major_slice_mut(
1072				s_U,
1073				s_col_index_count,
1074				s_size,
1075			)
1076			.transpose_mut();
1077			s_U.fill(zero());
1078			for i in s_begin..s_end {
1079				let pi = row_perm[i].zx();
1080				for (j, val) in
1081					iter::zip(AT.row_idx_of_col(pi), AT.val_of_col(pi))
1082				{
1083					let pj = col_perm_inv[j].zx();
1084					if pj < s_end {
1085						continue;
1086					}
1087					assert!(A_leftover > 0);
1088					A_leftover -= 1;
1089					let ix = i - s_begin;
1090					let iy = col_global_to_local[pj].zx();
1091					s_U[(ix, iy)] += val;
1092				}
1093			}
1094			noinline(UPanel, || {
1095				for i in s_begin..s_end {
1096					for d in &l_compress_rows[row_perm[i].zx()] {
1097						let d = d.zx();
1098						if supernode_visited[d].zx() < 2 * s + 2 {
1099							supernode_visited[d] = I(2 * s + 2);
1100
1101							let d = d.zx();
1102							if left_contrib[d].0.is_empty() {
1103								continue;
1104							}
1105							let d_begin = supernode_ptr[d].zx();
1106							let d_end = supernode_ptr[d + 1].zx();
1107							let d_size = d_end - d_begin;
1108							let d_row_idx =
1109								&left_row_idx[lu.l_col_ptr_for_row_idx[d].zx()
1110									..lu.l_col_ptr_for_row_idx[d + 1].zx()][d_size..];
1111							let d_col_idx = &lu.ut_row_idx[lu
1112								.ut_col_ptr_for_row_idx[d]
1113								.zx()
1114								..lu.ut_col_ptr_for_row_idx[d + 1].zx()];
1115
1116							let d_col_start =
1117								d_col_idx.partition_point(partition_fn(s_end));
1118							let d_LU = work_to_mat_mut(
1119								&mut left_contrib[d].0,
1120								d_row_idx.len(),
1121								d_col_idx.len(),
1122							);
1123							let mut d_LU = d_LU.get_mut(.., d_col_start..);
1124							let left_contrib = &mut left_contrib[d];
1125							let d_active = &mut left_contrib.1[d_col_start..];
1126							let d_active_count = &mut left_contrib.2;
1127							let d_active_mat = &mut left_contrib.3;
1128							for (d_j, j) in
1129								d_col_idx[d_col_start..].iter().enumerate()
1130							{
1131								if d_active[d_j] > I(0) {
1132									let mut taken_rows = 0usize;
1133									let j = j.zx();
1134									let s_j = col_global_to_local[j].zx();
1135									for (d_i, i) in d_row_idx.iter().enumerate()
1136									{
1137										let i = i.zx();
1138										let pi = row_perm_inv[i].zx();
1139										if pi >= s_begin && pi < s_end {
1140											let s_i =
1141												row_global_to_local[i].zx();
1142											s_U[(s_i, s_j)] -=
1143												&d_LU[(d_i, d_j)];
1144											d_LU[(d_i, d_j)] = zero::<T>();
1145											taken_rows += d_active_mat
1146												[(d_i, d_j + d_col_start)]
1147												as usize;
1148											d_active_mat
1149												[(d_i, d_j + d_col_start)] = 0;
1150										}
1151									}
1152									assert!(d_active[d_j] >= I(taken_rows));
1153									d_active[d_j] -= I(taken_rows);
1154									if d_active[d_j] == I(0) {
1155										assert!(*d_active_count > 0);
1156										*d_active_count -= 1;
1157									}
1158								}
1159							}
1160							if *d_active_count == 0 {
1161								left_contrib.0.clear();
1162								left_contrib.1 = alloc::vec::Vec::new();
1163								left_contrib.2 = 0;
1164								left_contrib.3 = MatU8::new();
1165							}
1166						}
1167					}
1168				}
1169			});
1170			linalg::triangular_solve::solve_unit_lower_triangular_in_place(
1171				s_L.rb().subrows(0, s_size),
1172				s_U.rb_mut(),
1173				par.reborrow(),
1174			);
1175			if s_row_idx_count > s_size && s_col_index_count > 0 {
1176				resize_vec::<T>(
1177					&mut right_contrib[0].0,
1178					from_wide_checked(
1179						to_wide(I(s_row_idx_count - s_size))
1180							* to_wide(I(s_col_index_count)),
1181					)?
1182					.zx(),
1183					false,
1184					false,
1185					zero::<T>(),
1186				)?;
1187				right_contrib[0]
1188					.1
1189					.try_reserve_exact(s_col_index_count)
1190					.ok()
1191					.ok_or(FaerError::OutOfMemory)?;
1192				right_contrib[0]
1193					.1
1194					.resize(s_col_index_count, I(s_row_idx_count - s_size));
1195				right_contrib[0].2 = s_col_index_count;
1196				right_contrib[0].3 = MatU8::with_dims(
1197					s_row_idx_count - s_size,
1198					s_col_index_count,
1199				)?;
1200				let mut s_LU = work_to_mat_mut(
1201					&mut right_contrib[0].0,
1202					s_row_idx_count - s_size,
1203					s_col_index_count,
1204				);
1205				linalg::matmul::matmul(
1206					s_LU.rb_mut(),
1207					Accum::Replace,
1208					s_L.rb().get(s_size.., ..),
1209					s_U.rb(),
1210					one::<T>(),
1211					par.reborrow(),
1212				);
1213				noinline(Front, || {
1214					for d in &u_compress_cols[s] {
1215						let d = d.zx();
1216						if left_contrib[d].0.is_empty() {
1217							continue;
1218						}
1219						let d_begin = supernode_ptr[d].zx();
1220						let d_end = supernode_ptr[d + 1].zx();
1221						let d_size = d_end - d_begin;
1222						let d_row_idx =
1223							&left_row_idx[lu.l_col_ptr_for_row_idx[d].zx()
1224								..lu.l_col_ptr_for_row_idx[d + 1].zx()][d_size..];
1225						let d_col_idx =
1226							&lu.ut_row_idx[lu.ut_col_ptr_for_row_idx[d].zx()
1227								..lu.ut_col_ptr_for_row_idx[d + 1].zx()];
1228						let contributes_to_front = d_row_idx
1229							.iter()
1230							.any(|&i| row_perm_inv[i.zx()].zx() >= s_end);
1231						if contributes_to_front {
1232							let d_col_start =
1233								d_col_idx.partition_point(partition_fn(s_end));
1234							let d_LU = work_to_mat_mut(
1235								&mut left_contrib[d].0,
1236								d_row_idx.len(),
1237								d_col_idx.len(),
1238							);
1239							let mut d_LU = d_LU.get_mut(.., d_col_start..);
1240							let left_contrib = &mut left_contrib[d];
1241							let d_active = &mut left_contrib.1[d_col_start..];
1242							let d_active_count = &mut left_contrib.2;
1243							let d_active_mat = &mut left_contrib.3;
1244							let mut d_active_row_count = 0usize;
1245							let mut first_iter = true;
1246							for (d_j, j) in
1247								d_col_idx[d_col_start..].iter().enumerate()
1248							{
1249								if d_active[d_j] > I(0) {
1250									if first_iter {
1251										first_iter = false;
1252										for (d_i, i) in
1253											d_row_idx.iter().enumerate()
1254										{
1255											let i = i.zx();
1256											let pi = row_perm_inv[i].zx();
1257											if (pi < s_end)
1258												|| (row_global_to_local[i]
1259													== I(NONE))
1260											{
1261												continue;
1262											}
1263											d_active_rows[d_active_row_count] =
1264												I(d_i);
1265											d_active_row_count += 1;
1266										}
1267									}
1268									let j = j.zx();
1269									let mut taken_rows = 0usize;
1270									let s_j = col_global_to_local[j];
1271									if s_j == I(NONE) {
1272										continue;
1273									}
1274									let s_j = s_j.zx();
1275									let mut dst = s_LU.rb_mut().col_mut(s_j);
1276									let mut src = d_LU.rb_mut().col_mut(d_j);
1277									assert!(dst.row_stride() == 1);
1278									assert!(src.row_stride() == 1);
1279									for d_i in
1280										&d_active_rows[..d_active_row_count]
1281									{
1282										let d_i = d_i.zx();
1283										let i = d_row_idx[d_i].zx();
1284										let d_active_mat = &mut d_active_mat
1285											[(d_i, d_j + d_col_start)];
1286										if *d_active_mat == 0 {
1287											continue;
1288										}
1289										let s_i = row_global_to_local[i].zx()
1290											- s_size;
1291										dst[s_i] += &src[d_i];
1292										src[d_i] = zero::<T>();
1293										taken_rows += 1;
1294										*d_active_mat = 0;
1295									}
1296									d_active[d_j] -= I(taken_rows);
1297									if d_active[d_j] == I(0) {
1298										*d_active_count -= 1;
1299									}
1300								}
1301							}
1302							if *d_active_count == 0 {
1303								left_contrib.0.clear();
1304								left_contrib.1 = alloc::vec::Vec::new();
1305								left_contrib.2 = 0;
1306								left_contrib.3 = MatU8::new();
1307							}
1308						}
1309					}
1310				})
1311			}
1312			for i in s_row_idx.iter() {
1313				row_global_to_local[i.zx()] = I(NONE);
1314			}
1315			for j in s_col_idx.iter() {
1316				col_global_to_local[j.zx()] = I(NONE);
1317			}
1318		}
1319		assert!(A_leftover == 0);
1320		for idx in
1321			&mut lu.l_row_idx[..lu.l_col_ptr_for_row_idx[n_supernodes].zx()]
1322		{
1323			*idx = row_perm_inv[idx.zx()];
1324		}
1325		lu.nrows = m;
1326		lu.ncols = n;
1327		lu.nsupernodes = n_supernodes;
1328		lu.supernode_ptr.clone_from(supernode_ptr);
1329		Ok(())
1330	}
1331}
1332
1333/// simplicial factorization module
1334///
1335/// a supernodal factorization is one that processes the elements of the $LU$
1336/// factors of the input matrix by single elements, rather than by blocks. this
1337/// is more efficient if the lu factors are very sparse
1338pub mod simplicial {
1339	use super::*;
1340	use crate::assert;
1341
1342	pub fn solve_in_place_scratch<I: Index, T: ComplexField>(
1343		dim: usize,
1344		rhs_ncols: usize,
1345		par: ParShape,
1346	) -> StackReq {
1347		let _ = par;
1348		crate::perm::permute_rows_in_place_scratch::<I, T>(dim, rhs_ncols)
1349	}
1350
1351	pub fn solve_transpose_in_place_scratch<I: Index, T: ComplexField>(
1352		dim: usize,
1353		rhs_ncols: usize,
1354		par: ParShape,
1355	) -> StackReq {
1356		solve_in_place_scratch::<I, T>(dim, rhs_ncols, par)
1357	}
1358
1359	/// $LU$ factor structure containing the symbolic and numerical
1360	/// representations
1361	#[derive(Debug, Clone)]
1362	pub struct SimplicialLu<I, T> {
1363		nrows: usize,
1364		ncols: usize,
1365		l_col_ptr: alloc::vec::Vec<I>,
1366		l_row_idx: alloc::vec::Vec<I>,
1367		l_val: alloc::vec::Vec<T>,
1368		u_col_ptr: alloc::vec::Vec<I>,
1369		u_row_idx: alloc::vec::Vec<I>,
1370		u_val: alloc::vec::Vec<T>,
1371	}
1372	impl<I: Index, T> Default for SimplicialLu<I, T> {
1373		fn default() -> Self {
1374			Self::new()
1375		}
1376	}
1377	impl<I: Index, T> SimplicialLu<I, T> {
1378		/// creates a new simplicial $LU$ of a $0 \times 0$ matrix
1379		#[inline]
1380		pub fn new() -> Self {
1381			Self {
1382				nrows: 0,
1383				ncols: 0,
1384				l_col_ptr: alloc::vec::Vec::new(),
1385				u_col_ptr: alloc::vec::Vec::new(),
1386				l_row_idx: alloc::vec::Vec::new(),
1387				u_row_idx: alloc::vec::Vec::new(),
1388				l_val: alloc::vec::Vec::new(),
1389				u_val: alloc::vec::Vec::new(),
1390			}
1391		}
1392
1393		/// returns the number of rows of $A$
1394		#[inline]
1395		pub fn nrows(&self) -> usize {
1396			self.nrows
1397		}
1398
1399		/// returns the number of columns of $A$
1400		#[inline]
1401		pub fn ncols(&self) -> usize {
1402			self.ncols
1403		}
1404
1405		/// returns the $L$ factor of the $LU$ factorization. the row indices
1406		/// may be unsorted
1407		#[inline]
1408		pub fn l_factor_unsorted(&self) -> SparseColMatRef<'_, I, T> {
1409			SparseColMatRef::<'_, I, T>::new(
1410				unsafe {
1411					SymbolicSparseColMatRef::new_unchecked(
1412						self.nrows(),
1413						self.ncols(),
1414						&self.l_col_ptr,
1415						None,
1416						&self.l_row_idx,
1417					)
1418				},
1419				&self.l_val,
1420			)
1421		}
1422
1423		/// returns the $U$ factor of the $LU$ factorization. the row indices
1424		/// may be unsorted
1425		#[inline]
1426		pub fn u_factor_unsorted(&self) -> SparseColMatRef<'_, I, T> {
1427			SparseColMatRef::<'_, I, T>::new(
1428				unsafe {
1429					SymbolicSparseColMatRef::new_unchecked(
1430						self.ncols(),
1431						self.ncols(),
1432						&self.u_col_ptr,
1433						None,
1434						&self.u_row_idx,
1435					)
1436				},
1437				&self.u_val,
1438			)
1439		}
1440
1441		/// solves the equation $A x = \text{rhs}$ and stores the result in
1442		/// `rhs`, implicitly conjugating $A$ if needed
1443		///
1444		/// # panics
1445		/// - panics if `self.nrows() != self.ncols()`
1446		/// - panics if `rhs.nrows() != self.nrows()`
1447		#[track_caller]
1448		pub fn solve_in_place_with_conj(
1449			&self,
1450			row_perm: PermRef<'_, I>,
1451			col_perm: PermRef<'_, I>,
1452			conj_lhs: Conj,
1453			rhs: MatMut<'_, T>,
1454			mut par: Par,
1455			stack: &mut MemStack,
1456		) where
1457			T: ComplexField,
1458		{
1459			assert!(self.nrows() == self.ncols());
1460			assert!(self.nrows() == rhs.nrows());
1461			let mut X = rhs;
1462			let l = self.l_factor_unsorted();
1463			let u = self.u_factor_unsorted();
1464			crate::perm::permute_rows_in_place(X.rb_mut(), row_perm, stack);
1465			linalg_sp::triangular_solve::solve_unit_lower_triangular_in_place(
1466				l,
1467				conj_lhs,
1468				X.rb_mut(),
1469				par.reborrow(),
1470			);
1471			linalg_sp::triangular_solve::solve_upper_triangular_in_place(
1472				u,
1473				conj_lhs,
1474				X.rb_mut(),
1475				par,
1476			);
1477			crate::perm::permute_rows_in_place(
1478				X.rb_mut(),
1479				col_perm.inverse(),
1480				stack,
1481			);
1482		}
1483
1484		/// solves the equation $A^\top x = \text{rhs}$ and stores the result in
1485		/// `rhs`, implicitly conjugating $A$ if needed
1486		///
1487		/// # panics
1488		/// - panics if `self.nrows() != self.ncols()`
1489		/// - panics if `rhs.nrows() != self.nrows()`
1490		#[track_caller]
1491		pub fn solve_transpose_in_place_with_conj(
1492			&self,
1493			row_perm: PermRef<'_, I>,
1494			col_perm: PermRef<'_, I>,
1495			conj_lhs: Conj,
1496			rhs: MatMut<'_, T>,
1497			mut par: Par,
1498			stack: &mut MemStack,
1499		) where
1500			T: ComplexField,
1501		{
1502			assert!(all(
1503				self.nrows() == self.ncols(),
1504				self.nrows() == rhs.nrows()
1505			));
1506			let mut X = rhs;
1507			let l = self.l_factor_unsorted();
1508			let u = self.u_factor_unsorted();
1509			crate::perm::permute_rows_in_place(X.rb_mut(), col_perm, stack);
1510			linalg_sp::triangular_solve::solve_upper_triangular_transpose_in_place(u, conj_lhs, X.rb_mut(), par.reborrow());
1511			linalg_sp::triangular_solve::solve_unit_lower_triangular_transpose_in_place(l, conj_lhs, X.rb_mut(), par);
1512			crate::perm::permute_rows_in_place(
1513				X.rb_mut(),
1514				row_perm.inverse(),
1515				stack,
1516			);
1517		}
1518	}
1519	fn depth_first_search<I: Index>(
1520		marked: &mut [I],
1521		mark: I,
1522		xi: &mut [I],
1523		l: SymbolicSparseColMatRef<'_, I>,
1524		row_perm_inv: &[I],
1525		b: usize,
1526		stack: &mut [I],
1527	) -> usize {
1528		let I = I::truncate;
1529		let mut tail_start = xi.len();
1530		let mut head_len = 1usize;
1531		xi[0] = I(b);
1532		let li = l.row_idx();
1533		'dfs_loop: while head_len > 0 {
1534			let b = xi[head_len - 1].zx().zx();
1535			let pb = row_perm_inv[b].zx();
1536			let range = if pb < l.ncols() {
1537				l.col_range(pb)
1538			} else {
1539				0..0
1540			};
1541			if marked[b] < mark {
1542				marked[b] = mark;
1543				stack[head_len - 1] = I(range.start);
1544			}
1545			let start = stack[head_len - 1].zx();
1546			let end = range.end;
1547			for ptr in start..end {
1548				let i = li[ptr].zx();
1549				if marked[i] == mark {
1550					continue;
1551				}
1552				stack[head_len - 1] = I(ptr);
1553				xi[head_len] = I(i);
1554				head_len += 1;
1555				continue 'dfs_loop;
1556			}
1557			head_len -= 1;
1558			tail_start -= 1;
1559			xi[tail_start] = I(b);
1560		}
1561		tail_start
1562	}
1563	fn reach<I: Index>(
1564		marked: &mut [I],
1565		mark: I,
1566		xi: &mut [I],
1567		l: SymbolicSparseColMatRef<'_, I>,
1568		row_perm_inv: &[I],
1569		bi: &[I],
1570		stack: &mut [I],
1571	) -> usize {
1572		let n = l.nrows();
1573		let mut tail_start = n;
1574		for b in bi {
1575			let b = b.zx();
1576			if marked[b] < mark {
1577				tail_start = depth_first_search(
1578					marked,
1579					mark,
1580					&mut xi[..tail_start],
1581					l,
1582					row_perm_inv,
1583					b,
1584					stack,
1585				);
1586			}
1587		}
1588		tail_start
1589	}
1590	fn l_incomplete_solve_sparse<I: Index, T: ComplexField>(
1591		marked: &mut [I],
1592		mark: I,
1593		xi: &mut [I],
1594		x: &mut [T],
1595		l: SparseColMatRef<'_, I, T>,
1596		row_perm_inv: &[I],
1597		bi: &[I],
1598		bx: &[T],
1599		stack: &mut [I],
1600	) -> usize {
1601		let tail_start =
1602			reach(marked, mark, xi, l.symbolic(), row_perm_inv, bi, stack);
1603		let xi = &xi[tail_start..];
1604		for (i, b) in iter::zip(bi, bx) {
1605			let i = i.zx();
1606			x[i] += b;
1607		}
1608		for i in xi {
1609			let i = i.zx();
1610			let pi = row_perm_inv[i].zx();
1611			if pi >= l.ncols() {
1612				continue;
1613			}
1614			let li = l.row_idx_of_col_raw(pi);
1615			let lx = l.val_of_col(pi);
1616			let len = li.len();
1617			let ref xi = x[i].copy();
1618			for (li, lx) in iter::zip(&li[1..], &lx[1..len]) {
1619				let li = li.zx();
1620				x[li] -= lx * xi;
1621			}
1622		}
1623		tail_start
1624	}
1625	/// computes the layout of the workspace required to perform a numeric $LU$
1626	/// factorization
1627	pub fn factorize_simplicial_numeric_lu_scratch<
1628		I: Index,
1629		T: ComplexField,
1630	>(
1631		nrows: usize,
1632		ncols: usize,
1633	) -> StackReq {
1634		let idx = StackReq::new::<I>(nrows);
1635		let val = temp_mat_scratch::<T>(nrows, 1);
1636		let _ = ncols;
1637		StackReq::all_of(&[val, idx, idx, idx])
1638	}
1639	/// computes the numeric values of the $LU$ factors of the matrix $A$ as
1640	/// well as the row pivoting permutation, and stores them in `lu` and
1641	/// `row_perm`/`row_perm_inv`
1642	pub fn factorize_simplicial_numeric_lu<I: Index, T: ComplexField>(
1643		row_perm: &mut [I],
1644		row_perm_inv: &mut [I],
1645		lu: &mut SimplicialLu<I, T>,
1646		A: SparseColMatRef<'_, I, T>,
1647		col_perm: PermRef<'_, I>,
1648		stack: &mut MemStack,
1649	) -> Result<(), LuError> {
1650		let I = I::truncate;
1651		assert!(all(
1652			A.nrows() == row_perm.len(),
1653			A.nrows() == row_perm_inv.len(),
1654			A.ncols() == col_perm.len(),
1655			A.nrows() == A.ncols()
1656		));
1657		lu.nrows = 0;
1658		lu.ncols = 0;
1659		let m = A.nrows();
1660		let n = A.ncols();
1661		resize_vec(&mut lu.l_col_ptr, n + 1, true, false, I(0))?;
1662		resize_vec(&mut lu.u_col_ptr, n + 1, true, false, I(0))?;
1663		let (mut x, stack) = temp_mat_zeroed::<T, _, _>(m, 1, stack);
1664		let x = x
1665			.as_mat_mut()
1666			.col_mut(0)
1667			.try_as_col_major_mut()
1668			.unwrap()
1669			.as_slice_mut();
1670		let (marked, stack) = unsafe { stack.make_raw::<I>(m) };
1671		let (xj, stack) = unsafe { stack.make_raw::<I>(m) };
1672		let (stack, _) = unsafe { stack.make_raw::<I>(m) };
1673		marked.fill(I(0));
1674		row_perm_inv.fill(I(n));
1675		let mut l_pos = 0usize;
1676		let mut u_pos = 0usize;
1677		lu.l_col_ptr[0] = I(0);
1678		lu.u_col_ptr[0] = I(0);
1679		for j in 0..n {
1680			let l = SparseColMatRef::<'_, I, T>::new(
1681				unsafe {
1682					SymbolicSparseColMatRef::new_unchecked(
1683						m,
1684						j,
1685						&lu.l_col_ptr[..j + 1],
1686						None,
1687						&lu.l_row_idx,
1688					)
1689				},
1690				&lu.l_val,
1691			);
1692			let pj = col_perm.arrays().0[j].zx();
1693			let tail_start = l_incomplete_solve_sparse(
1694				marked,
1695				I(j + 1),
1696				xj,
1697				x,
1698				l,
1699				row_perm_inv,
1700				A.row_idx_of_col_raw(pj),
1701				A.val_of_col(pj),
1702				stack,
1703			);
1704			let xj = &xj[tail_start..];
1705			resize_vec::<T>(
1706				&mut lu.l_val,
1707				l_pos + xj.len() + 1,
1708				false,
1709				false,
1710				zero::<T>(),
1711			)?;
1712			resize_vec(
1713				&mut lu.l_row_idx,
1714				l_pos + xj.len() + 1,
1715				false,
1716				false,
1717				I(0),
1718			)?;
1719			resize_vec::<T>(
1720				&mut lu.u_val,
1721				u_pos + xj.len() + 1,
1722				false,
1723				false,
1724				zero::<T>(),
1725			)?;
1726			resize_vec(
1727				&mut lu.u_row_idx,
1728				u_pos + xj.len() + 1,
1729				false,
1730				false,
1731				I(0),
1732			)?;
1733			let l_val = &mut *lu.l_val;
1734			let u_val = &mut *lu.u_val;
1735			let mut pivot_idx = n;
1736			let mut pivot_val = -one::<T::Real>();
1737			for i in xj {
1738				let i = i.zx();
1739				let xi = x[i].copy();
1740				if row_perm_inv[i] == I(n) {
1741					let val = xi.abs();
1742					if matches!(
1743						val.partial_cmp(&pivot_val),
1744						None | Some(core::cmp::Ordering::Greater)
1745					) {
1746						pivot_idx = i;
1747						pivot_val = val;
1748					}
1749				} else {
1750					lu.u_row_idx[u_pos] = row_perm_inv[i];
1751					u_val[u_pos] = xi;
1752					u_pos += 1;
1753				}
1754			}
1755			if pivot_idx == n {
1756				return Err(LuError::SymbolicSingular { index: j });
1757			}
1758			let x_piv = x[pivot_idx].copy();
1759			// the pivot itself, not `pivot_val`: a complex modulus can
1760			// underflow to zero for a nonzero subnormal entry
1761			if x_piv.abs1() == zero::<T::Real>() {
1762				return Err(LuError::NumericSingular { index: j });
1763			}
1764			let ref x_piv_inv = x_piv.recip();
1765			row_perm_inv[pivot_idx] = I(j);
1766			lu.u_row_idx[u_pos] = I(j);
1767			u_val[u_pos] = x_piv;
1768			u_pos += 1;
1769			lu.u_col_ptr[j + 1] = I(u_pos);
1770			lu.l_row_idx[l_pos] = I(pivot_idx);
1771			l_val[l_pos] = one::<T>();
1772			l_pos += 1;
1773			for i in xj {
1774				let i = i.zx();
1775				let xi = x[i].copy();
1776				if row_perm_inv[i] == I(n) {
1777					lu.l_row_idx[l_pos] = I(i);
1778					l_val[l_pos] = xi * x_piv_inv;
1779					l_pos += 1;
1780				}
1781				x[i] = zero::<T>();
1782			}
1783			lu.l_col_ptr[j + 1] = I(l_pos);
1784		}
1785		for i in &mut lu.l_row_idx[..l_pos] {
1786			*i = row_perm_inv[(*i).zx()];
1787		}
1788		for (idx, p) in row_perm_inv.iter().enumerate() {
1789			row_perm[p.zx()] = I(idx);
1790		}
1791		lu.nrows = m;
1792		lu.ncols = n;
1793		Ok(())
1794	}
1795}
1796/// tuning parameters for the $LU$ symbolic factorization
1797#[derive(Copy, Clone, Debug, Default)]
1798pub struct LuSymbolicParams<'a> {
1799	/// parameters for the fill reducing column permutation
1800	pub colamd_params: colamd::Control,
1801	/// threshold for selecting the supernodal factorization
1802	pub supernodal_flop_ratio_threshold: SupernodalThreshold,
1803	/// supernodal factorization parameters
1804	pub supernodal_params: SymbolicSupernodalParams<'a>,
1805}
1806/// the inner factorization used for the symbolic $LU$, either simplicial or
1807/// symbolic
1808#[derive(Debug, Clone)]
1809pub enum SymbolicLuRaw<I: Index> {
1810	/// simplicial structure
1811	Simplicial {
1812		/// number of rows of $A$
1813		nrows: usize,
1814		/// number of columns of $A$
1815		ncols: usize,
1816	},
1817	/// supernodal structure
1818	Supernodal(supernodal::SymbolicSupernodalLu<I>),
1819}
1820/// the symbolic structure of a sparse $LU$ decomposition
1821#[derive(Debug, Clone)]
1822pub struct SymbolicLu<I: Index> {
1823	raw: SymbolicLuRaw<I>,
1824	col_perm_fwd: alloc::vec::Vec<I>,
1825	col_perm_inv: alloc::vec::Vec<I>,
1826	A_nnz: usize,
1827}
1828#[derive(Debug, Clone)]
1829enum NumericLuRaw<I: Index, T> {
1830	None,
1831	Supernodal(supernodal::SupernodalLu<I, T>),
1832	Simplicial(simplicial::SimplicialLu<I, T>),
1833}
1834/// structure that contains the numerical values and row pivoting permutation of
1835/// the lu decomposition
1836#[derive(Debug, Clone)]
1837pub struct NumericLu<I: Index, T> {
1838	raw: NumericLuRaw<I, T>,
1839	row_perm_fwd: alloc::vec::Vec<I>,
1840	row_perm_inv: alloc::vec::Vec<I>,
1841}
1842impl<I: Index, T> Default for NumericLu<I, T> {
1843	fn default() -> Self {
1844		Self::new()
1845	}
1846}
1847impl<I: Index, T> NumericLu<I, T> {
1848	/// creates a new $LU$ of a $0\times 0$ matrix
1849	#[inline]
1850	pub fn new() -> Self {
1851		Self {
1852			raw: NumericLuRaw::None,
1853			row_perm_fwd: alloc::vec::Vec::new(),
1854			row_perm_inv: alloc::vec::Vec::new(),
1855		}
1856	}
1857}
1858/// sparse $LU$ factorization wrapper
1859#[derive(Debug)]
1860pub struct LuRef<'a, I: Index, T> {
1861	symbolic: &'a SymbolicLu<I>,
1862	numeric: &'a NumericLu<I, T>,
1863}
1864impl<I: Index, T> Copy for LuRef<'_, I, T> {}
1865impl<I: Index, T> Clone for LuRef<'_, I, T> {
1866	fn clone(&self) -> Self {
1867		*self
1868	}
1869}
1870impl<'a, I: Index, T> LuRef<'a, I, T> {
1871	/// creates $LU$ factors from their components
1872	///
1873	/// # safety
1874	/// the numeric part must be the output of
1875	/// [`SymbolicLu::factorize_numeric_lu`], called with a matrix having the
1876	/// same symbolic structure as the one used to create `symbolic`
1877	#[inline]
1878	pub fn new_unchecked(
1879		symbolic: &'a SymbolicLu<I>,
1880		numeric: &'a NumericLu<I, T>,
1881	) -> Self {
1882		match (&symbolic.raw, &numeric.raw) {
1883			(SymbolicLuRaw::Simplicial { .. }, NumericLuRaw::Simplicial(_)) => {
1884			},
1885			(SymbolicLuRaw::Supernodal { .. }, NumericLuRaw::Supernodal(_)) => {
1886			},
1887			_ => panic!("incompatible symbolic and numeric variants"),
1888		}
1889		Self { symbolic, numeric }
1890	}
1891
1892	/// returns the symbolic structure of the $LU$ factorization
1893	#[inline]
1894	pub fn symbolic(self) -> &'a SymbolicLu<I> {
1895		self.symbolic
1896	}
1897
1898	/// returns the row pivoting permutation
1899	#[inline]
1900	pub fn row_perm(self) -> PermRef<'a, I> {
1901		unsafe {
1902			PermRef::new_unchecked(
1903				&self.numeric.row_perm_fwd,
1904				&self.numeric.row_perm_inv,
1905				self.symbolic.nrows(),
1906			)
1907		}
1908	}
1909
1910	/// returns the fill reducing column permutation
1911	#[inline]
1912	pub fn col_perm(self) -> PermRef<'a, I> {
1913		self.symbolic.col_perm()
1914	}
1915
1916	/// solves the equation $A x = \text{rhs}$ and stores the result in `rhs`,
1917	/// implicitly conjugating $A$ if needed
1918	///
1919	/// # panics
1920	/// - panics if `self.nrows() != self.ncols()`
1921	/// - panics if `rhs.nrows() != self.nrows()`
1922	#[track_caller]
1923	pub fn solve_in_place_with_conj(
1924		self,
1925		conj: Conj,
1926		rhs: MatMut<'_, T>,
1927		par: Par,
1928		stack: &mut MemStack,
1929	) where
1930		T: ComplexField,
1931	{
1932		match (&self.symbolic.raw, &self.numeric.raw) {
1933			(
1934				SymbolicLuRaw::Simplicial { .. },
1935				NumericLuRaw::Simplicial(numeric),
1936			) => numeric.solve_in_place_with_conj(
1937				self.row_perm(),
1938				self.col_perm(),
1939				conj,
1940				rhs,
1941				par,
1942				stack,
1943			),
1944			(
1945				SymbolicLuRaw::Supernodal(_),
1946				NumericLuRaw::Supernodal(numeric),
1947			) => numeric.solve_in_place_with_conj(
1948				self.row_perm(),
1949				self.col_perm(),
1950				conj,
1951				rhs,
1952				par,
1953				stack,
1954			),
1955			_ => unreachable!(),
1956		}
1957	}
1958
1959	/// solves the equation $A^\top x = \text{rhs}$ and stores the result in
1960	/// `rhs`, implicitly conjugating $A$ if needed
1961	///
1962	/// # panics
1963	/// - panics if `self.nrows() != self.ncols()`
1964	/// - panics if `rhs.nrows() != self.nrows()`
1965	#[track_caller]
1966	pub fn solve_transpose_in_place_with_conj(
1967		self,
1968		conj: Conj,
1969		rhs: MatMut<'_, T>,
1970		par: Par,
1971		stack: &mut MemStack,
1972	) where
1973		T: ComplexField,
1974	{
1975		match (&self.symbolic.raw, &self.numeric.raw) {
1976			(
1977				SymbolicLuRaw::Simplicial { .. },
1978				NumericLuRaw::Simplicial(numeric),
1979			) => numeric.solve_transpose_in_place_with_conj(
1980				self.row_perm(),
1981				self.col_perm(),
1982				conj,
1983				rhs,
1984				par,
1985				stack,
1986			),
1987			(
1988				SymbolicLuRaw::Supernodal(_),
1989				NumericLuRaw::Supernodal(numeric),
1990			) => numeric.solve_transpose_in_place_with_conj(
1991				self.row_perm(),
1992				self.col_perm(),
1993				conj,
1994				rhs,
1995				par,
1996				stack,
1997			),
1998			_ => unreachable!(),
1999		}
2000	}
2001}
2002impl<I: Index> SymbolicLu<I> {
2003	/// returns the number of rows of $A$
2004	#[inline]
2005	pub fn nrows(&self) -> usize {
2006		match &self.raw {
2007			SymbolicLuRaw::Simplicial { nrows, .. } => *nrows,
2008			SymbolicLuRaw::Supernodal(this) => this.nrows,
2009		}
2010	}
2011
2012	/// returns the number of columns of $A$
2013	#[inline]
2014	pub fn ncols(&self) -> usize {
2015		match &self.raw {
2016			SymbolicLuRaw::Simplicial { ncols, .. } => *ncols,
2017			SymbolicLuRaw::Supernodal(this) => this.ncols,
2018		}
2019	}
2020
2021	/// returns the fill-reducing column permutation that was computed during
2022	/// symbolic analysis
2023	#[inline]
2024	pub fn col_perm(&self) -> PermRef<'_, I> {
2025		unsafe {
2026			PermRef::new_unchecked(
2027				&self.col_perm_fwd,
2028				&self.col_perm_inv,
2029				self.ncols(),
2030			)
2031		}
2032	}
2033
2034	/// computes the layout of the workspace required to compute the numerical
2035	/// $LU$ factorization
2036	pub fn factorize_numeric_lu_scratch<T>(
2037		&self,
2038		par: ParShape,
2039		params: Spec<PartialPivLuParams, T>,
2040	) -> StackReq
2041	where
2042		T: ComplexField,
2043	{
2044		match &self.raw {
2045			SymbolicLuRaw::Simplicial { nrows, ncols } => {
2046				simplicial::factorize_simplicial_numeric_lu_scratch::<I, T>(
2047					*nrows, *ncols,
2048				)
2049			},
2050			SymbolicLuRaw::Supernodal(symbolic) => {
2051				let _ = par;
2052				let m = symbolic.nrows;
2053				let A_nnz = self.A_nnz;
2054				let AT_scratch = StackReq::all_of(&[
2055					temp_mat_scratch::<T>(A_nnz, 1),
2056					StackReq::new::<I>(m + 1),
2057					StackReq::new::<I>(A_nnz),
2058				]);
2059				StackReq::and(
2060					AT_scratch,
2061					supernodal::factorize_supernodal_numeric_lu_scratch::<I, T>(
2062						symbolic, params,
2063					),
2064				)
2065			},
2066		}
2067	}
2068
2069	/// computes the layout of the workspace required to solve the equation $A x
2070	/// = b$
2071	pub fn solve_in_place_scratch<T>(
2072		&self,
2073		rhs_ncols: usize,
2074		par: ParShape,
2075	) -> StackReq
2076	where
2077		T: ComplexField,
2078	{
2079		match &self.raw {
2080			SymbolicLuRaw::Simplicial { nrows, .. } => {
2081				simplicial::solve_in_place_scratch::<I, T>(
2082					*nrows, rhs_ncols, par,
2083				)
2084			},
2085			SymbolicLuRaw::Supernodal(this) => {
2086				supernodal::solve_in_place_scratch::<I, T>(
2087					this.nrows, rhs_ncols, par,
2088				)
2089			},
2090		}
2091	}
2092
2093	/// computes the layout of the workspace required to solve the equation
2094	/// $A^\top x = b$
2095	pub fn solve_transpose_in_place_scratch<T>(
2096		&self,
2097		rhs_ncols: usize,
2098		par: ParShape,
2099	) -> StackReq
2100	where
2101		T: ComplexField,
2102	{
2103		self.solve_in_place_scratch::<T>(rhs_ncols, par)
2104	}
2105
2106	/// computes a numerical $LU$ factorization of $A$
2107	#[track_caller]
2108	pub fn factorize_numeric_lu<'out, T: ComplexField>(
2109		&'out self,
2110		numeric: &'out mut NumericLu<I, T>,
2111		A: SparseColMatRef<'_, I, T>,
2112		par: Par,
2113		stack: &mut MemStack,
2114		params: Spec<PartialPivLuParams, T>,
2115	) -> Result<LuRef<'out, I, T>, LuError> {
2116		if matches!(self.raw, SymbolicLuRaw::Simplicial { .. })
2117			&& !matches!(numeric.raw, NumericLuRaw::Simplicial(_))
2118		{
2119			numeric.raw =
2120				NumericLuRaw::Simplicial(simplicial::SimplicialLu::new());
2121		}
2122		if matches!(self.raw, SymbolicLuRaw::Supernodal(_))
2123			&& !matches!(numeric.raw, NumericLuRaw::Supernodal(_))
2124		{
2125			numeric.raw =
2126				NumericLuRaw::Supernodal(supernodal::SupernodalLu::new());
2127		}
2128		let nrows = self.nrows();
2129		numeric
2130			.row_perm_fwd
2131			.try_reserve_exact(nrows.saturating_sub(numeric.row_perm_fwd.len()))
2132			.ok()
2133			.ok_or(FaerError::OutOfMemory)?;
2134		numeric
2135			.row_perm_inv
2136			.try_reserve_exact(nrows.saturating_sub(numeric.row_perm_inv.len()))
2137			.ok()
2138			.ok_or(FaerError::OutOfMemory)?;
2139		numeric.row_perm_fwd.resize(nrows, I::truncate(0));
2140		numeric.row_perm_inv.resize(nrows, I::truncate(0));
2141		match (&self.raw, &mut numeric.raw) {
2142			(
2143				SymbolicLuRaw::Simplicial { nrows, ncols },
2144				NumericLuRaw::Simplicial(lu),
2145			) => {
2146				assert!(all(A.nrows() == *nrows, A.ncols() == *ncols));
2147				simplicial::factorize_simplicial_numeric_lu(
2148					&mut numeric.row_perm_fwd,
2149					&mut numeric.row_perm_inv,
2150					lu,
2151					A,
2152					self.col_perm(),
2153					stack,
2154				)?;
2155			},
2156			(
2157				SymbolicLuRaw::Supernodal(symbolic),
2158				NumericLuRaw::Supernodal(lu),
2159			) => {
2160				let m = symbolic.nrows;
2161				let (new_col_ptr, stack) =
2162					unsafe { stack.make_raw::<I>(m + 1) };
2163				let (new_row_idx, stack) =
2164					unsafe { stack.make_raw::<I>(self.A_nnz) };
2165				let (mut new_values, stack) =
2166					unsafe { temp_mat_uninit::<T, _, _>(self.A_nnz, 1, stack) };
2167				let new_values = new_values
2168					.as_mat_mut()
2169					.col_mut(0)
2170					.try_as_col_major_mut()
2171					.unwrap()
2172					.as_slice_mut();
2173				let AT = utils::transpose(
2174					new_values,
2175					new_col_ptr,
2176					new_row_idx,
2177					A,
2178					stack,
2179				)
2180				.into_const();
2181				supernodal::factorize_supernodal_numeric_lu(
2182					&mut numeric.row_perm_fwd,
2183					&mut numeric.row_perm_inv,
2184					lu,
2185					A,
2186					AT,
2187					self.col_perm(),
2188					symbolic,
2189					par,
2190					stack,
2191					params,
2192				)?;
2193			},
2194			_ => unreachable!(),
2195		}
2196		Ok(LuRef::new_unchecked(self, numeric))
2197	}
2198}
2199/// computes the symbolic $LU$ factorization of the matrix $A$, or returns an
2200/// error if the operation could not be completed
2201#[track_caller]
2202pub fn factorize_symbolic_lu<I: Index>(
2203	A: SymbolicSparseColMatRef<'_, I>,
2204	params: LuSymbolicParams<'_>,
2205) -> Result<SymbolicLu<I>, FaerError> {
2206	assert!(A.nrows() == A.ncols());
2207	let m = A.nrows();
2208	let n = A.ncols();
2209	let A_nnz = A.compute_nnz();
2210	with_dim!(M, m);
2211	with_dim!(N, n);
2212	let A = A.as_shape(M, N);
2213	let req = {
2214		let n_scratch = StackReq::new::<I>(n);
2215		let m_scratch = StackReq::new::<I>(m);
2216		let AT_scratch =
2217			StackReq::and(StackReq::new::<I>(m + 1), StackReq::new::<I>(A_nnz));
2218		StackReq::or(
2219			linalg_sp::colamd::order_scratch::<I>(m, n, A_nnz),
2220			StackReq::all_of(&[
2221				n_scratch,
2222				n_scratch,
2223				n_scratch,
2224				n_scratch,
2225				AT_scratch,
2226				StackReq::any_of(&[
2227					StackReq::and(n_scratch, m_scratch),
2228					StackReq::all_of(&[n_scratch; 3]),
2229					StackReq::all_of(&[
2230						n_scratch, n_scratch, n_scratch, n_scratch, n_scratch,
2231						m_scratch,
2232					]),
2233					supernodal::factorize_supernodal_symbolic_lu_scratch::<I>(
2234						m, n,
2235					),
2236				]),
2237			]),
2238		)
2239	};
2240	let mut mem = dyn_stack::MemBuffer::try_new(req)
2241		.ok()
2242		.ok_or(FaerError::OutOfMemory)?;
2243	let stack = MemStack::new(&mut mem);
2244	let mut col_perm_fwd = try_zeroed::<I>(n)?;
2245	let mut col_perm_inv = try_zeroed::<I>(n)?;
2246	let mut min_row = try_zeroed::<I>(m)?;
2247	linalg_sp::colamd::order(
2248		&mut col_perm_fwd,
2249		&mut col_perm_inv,
2250		A.as_dyn(),
2251		params.colamd_params,
2252		stack,
2253	)?;
2254	let col_perm =
2255		PermRef::new_checked(&col_perm_fwd, &col_perm_inv, n).as_shape(N);
2256	let (new_col_ptr, stack) = unsafe { stack.make_raw::<I>(m + 1) };
2257	let (new_row_idx, stack) = unsafe { stack.make_raw::<I>(A_nnz) };
2258	let AT = utils::adjoint(
2259		Symbolic::materialize(new_row_idx.len()),
2260		new_col_ptr,
2261		new_row_idx,
2262		SparseColMatRef::new(A, Symbolic::materialize(A.row_idx().len())),
2263		stack,
2264	)
2265	.symbolic();
2266	let (etree, stack) = unsafe { stack.make_raw::<I::Signed>(n) };
2267	let (post, stack) = unsafe { stack.make_raw::<I>(n) };
2268	let (col_counts, stack) = unsafe { stack.make_raw::<I>(n) };
2269	let (h_col_counts, stack) = unsafe { stack.make_raw::<I>(n) };
2270	linalg_sp::qr::ghost_col_etree(
2271		A,
2272		Some(col_perm),
2273		Array::from_mut(etree, N),
2274		stack,
2275	);
2276	let etree_ =
2277		Array::from_ref(MaybeIdx::<'_, I>::from_slice_ref_checked(etree, N), N);
2278	linalg_sp::cholesky::ghost_postorder(
2279		Array::from_mut(post, N),
2280		etree_,
2281		stack,
2282	);
2283	linalg_sp::qr::ghost_column_counts_aat(
2284		Array::from_mut(col_counts, N),
2285		Array::from_mut(bytemuck::cast_slice_mut(&mut min_row), M),
2286		AT,
2287		Some(col_perm),
2288		etree_,
2289		Array::from_ref(Idx::from_slice_ref_checked(post, N), N),
2290		stack,
2291	);
2292	let min_col = min_row;
2293	let mut threshold = params.supernodal_flop_ratio_threshold;
2294	if threshold != SupernodalThreshold::FORCE_SIMPLICIAL
2295		&& threshold != SupernodalThreshold::FORCE_SUPERNODAL
2296	{
2297		h_col_counts.fill(I::truncate(0));
2298		for i in 0..m {
2299			let min_col = min_col[i];
2300			if min_col.to_signed() < I::Signed::truncate(0) {
2301				continue;
2302			}
2303			h_col_counts[min_col.zx()] += I::truncate(1);
2304		}
2305		for j in 0..n {
2306			let parent = etree[j];
2307			if parent < I::Signed::truncate(0) {
2308				continue;
2309			}
2310			h_col_counts[parent.zx()] +=
2311				I::truncate(h_col_counts[j].zx().saturating_sub(1));
2312		}
2313		let mut nnz = 0.0f64;
2314		let mut flops = 0.0f64;
2315		for j in 0..n {
2316			let hj = h_col_counts[j].zx() as f64;
2317			let rj = col_counts[j].zx() as f64;
2318			flops += hj + hj * rj;
2319			nnz += hj + rj;
2320		}
2321		if flops / nnz > threshold.0 * linalg_sp::LU_SUPERNODAL_RATIO_FACTOR {
2322			threshold = SupernodalThreshold::FORCE_SUPERNODAL;
2323		} else {
2324			threshold = SupernodalThreshold::FORCE_SIMPLICIAL;
2325		}
2326	}
2327	if threshold == SupernodalThreshold::FORCE_SUPERNODAL {
2328		let symbolic = supernodal::factorize_supernodal_symbolic_lu::<I>(
2329			A.as_dyn(),
2330			Some(col_perm.as_shape(n)),
2331			&min_col,
2332			EliminationTreeRef::<'_, I> { inner: etree },
2333			col_counts,
2334			stack,
2335			params.supernodal_params,
2336		)?;
2337		Ok(SymbolicLu {
2338			raw: SymbolicLuRaw::Supernodal(symbolic),
2339			col_perm_fwd,
2340			col_perm_inv,
2341			A_nnz,
2342		})
2343	} else {
2344		Ok(SymbolicLu {
2345			raw: SymbolicLuRaw::Simplicial { nrows: m, ncols: n },
2346			col_perm_fwd,
2347			col_perm_inv,
2348			A_nnz,
2349		})
2350	}
2351}
2352#[cfg(test)]
2353mod tests {
2354	use super::*;
2355	use crate::assert;
2356	use crate::stats::prelude::*;
2357	use dyn_stack::MemBuffer;
2358	use linalg_sp::cholesky::tests::load_mtx;
2359	use matrix_market_rs::MtxData;
2360	use std::path::PathBuf;
2361
2362	#[test]
2363	fn test_numeric_lu_multifrontal() {
2364		type T = c64;
2365		let (m, n, col_ptr, row_idx, val) = load_mtx::<usize>(
2366			MtxData::from_file(
2367				PathBuf::from(env!("CARGO_MANIFEST_DIR"))
2368					.join("test_data/sparse_lu/YAO.mtx"),
2369			)
2370			.unwrap(),
2371		);
2372		let mut rng = StdRng::seed_from_u64(0);
2373		let mut gen = || T::new(rng.random::<f64>(), rng.random::<f64>());
2374		let val = val.iter().map(|_| gen()).collect::<alloc::vec::Vec<_>>();
2375		let A = SparseColMatRef::<'_, usize, T>::new(
2376			SymbolicSparseColMatRef::new_checked(
2377				m, n, &col_ptr, None, &row_idx,
2378			),
2379			&val,
2380		);
2381		let mut row_perm = vec![0usize; n];
2382		let mut row_perm_inv = vec![0usize; n];
2383		let mut col_perm = vec![0usize; n];
2384		let mut col_perm_inv = vec![0usize; n];
2385		for i in 0..n {
2386			col_perm[i] = i;
2387			col_perm_inv[i] = i;
2388		}
2389		let col_perm =
2390			PermRef::<'_, usize>::new_checked(&col_perm, &col_perm_inv, n);
2391		let mut etree = vec![0usize; n];
2392		let mut min_col = vec![0usize; m];
2393		let mut col_counts = vec![0usize; n];
2394		let nnz = A.compute_nnz();
2395		let mut new_col_ptr = vec![0usize; m + 1];
2396		let mut new_row_idx = vec![0usize; nnz];
2397		let mut new_values = vec![zero::<T>(); nnz];
2398		let AT = utils::transpose(
2399			&mut *new_values,
2400			&mut new_col_ptr,
2401			&mut new_row_idx,
2402			A,
2403			MemStack::new(&mut MemBuffer::new(StackReq::new::<usize>(m))),
2404		)
2405		.into_const();
2406		let etree = {
2407			let mut post = vec![0usize; n];
2408			let etree = linalg_sp::qr::col_etree(
2409				A.symbolic(),
2410				Some(col_perm),
2411				&mut etree,
2412				MemStack::new(&mut MemBuffer::new(StackReq::new::<usize>(
2413					m + n,
2414				))),
2415			);
2416			linalg_sp::qr::postorder(
2417				&mut post,
2418				etree,
2419				MemStack::new(&mut MemBuffer::new(StackReq::new::<usize>(
2420					3 * n,
2421				))),
2422			);
2423			linalg_sp::qr::column_counts_ata(
2424				&mut col_counts,
2425				&mut min_col,
2426				AT.symbolic(),
2427				Some(col_perm),
2428				etree,
2429				&post,
2430				MemStack::new(&mut MemBuffer::new(StackReq::new::<usize>(
2431					5 * n + m,
2432				))),
2433			);
2434			etree
2435		};
2436		let symbolic = linalg_sp::lu::supernodal::factorize_supernodal_symbolic_lu::<usize>(
2437			A.symbolic(),
2438			Some(col_perm),
2439			&min_col,
2440			etree,
2441			&col_counts,
2442			MemStack::new(&mut MemBuffer::new(super::supernodal::factorize_supernodal_symbolic_lu_scratch::<usize>(
2443				m, n,
2444			))),
2445			linalg_sp::SymbolicSupernodalParams {
2446				relax: Some(&[(4, 1.0), (16, 0.8), (48, 0.1), (usize::MAX, 0.05)]),
2447			},
2448		)
2449		.unwrap();
2450		let mut lu = supernodal::SupernodalLu::<usize, T>::new();
2451		supernodal::factorize_supernodal_numeric_lu(
2452			&mut row_perm,
2453			&mut row_perm_inv,
2454			&mut lu,
2455			A,
2456			AT,
2457			col_perm,
2458			&symbolic,
2459			Par::Seq,
2460			MemStack::new(&mut MemBuffer::new(
2461				supernodal::factorize_supernodal_numeric_lu_scratch::<usize, T>(
2462					&symbolic,
2463					Default::default(),
2464				),
2465			)),
2466			Default::default(),
2467		)
2468		.unwrap();
2469		let k = 2;
2470		let rhs = Mat::from_fn(n, k, |_, _| gen());
2471		let A_dense = A.to_dense();
2472		let row_perm =
2473			PermRef::<'_, _>::new_checked(&row_perm, &row_perm_inv, m);
2474		{
2475			let mut x = rhs.clone();
2476			lu.solve_in_place_with_conj(
2477				row_perm,
2478				col_perm,
2479				Conj::No,
2480				x.as_mut(),
2481				Par::Seq,
2482				MemStack::new(&mut MemBuffer::new(
2483					supernodal::solve_in_place_scratch::<usize, T>(
2484						n,
2485						k,
2486						ParShape::Seq,
2487					),
2488				)),
2489			);
2490			assert!((&A_dense * &x - &rhs).norm_max() < 1e-10);
2491		}
2492		{
2493			let mut x = rhs.clone();
2494			lu.solve_in_place_with_conj(
2495				row_perm,
2496				col_perm,
2497				Conj::Yes,
2498				x.as_mut(),
2499				Par::Seq,
2500				MemStack::new(&mut MemBuffer::new(
2501					supernodal::solve_in_place_scratch::<usize, T>(
2502						n,
2503						k,
2504						ParShape::Seq,
2505					),
2506				)),
2507			);
2508			assert!((A_dense.conjugate() * &x - &rhs).norm_max() < 1e-10);
2509		}
2510		{
2511			let mut x = rhs.clone();
2512			lu.solve_transpose_in_place_with_conj(
2513				row_perm,
2514				col_perm,
2515				Conj::No,
2516				x.as_mut(),
2517				Par::Seq,
2518				MemStack::new(&mut MemBuffer::new(
2519					supernodal::solve_transpose_in_place_scratch::<usize, T>(
2520						n,
2521						k,
2522						ParShape::Seq,
2523					),
2524				)),
2525			);
2526			assert!((A_dense.transpose() * &x - &rhs).norm_max() < 1e-10);
2527		}
2528		{
2529			let mut x = rhs.clone();
2530			lu.solve_transpose_in_place_with_conj(
2531				row_perm,
2532				col_perm,
2533				Conj::Yes,
2534				x.as_mut(),
2535				Par::Seq,
2536				MemStack::new(&mut MemBuffer::new(
2537					supernodal::solve_transpose_in_place_scratch::<usize, T>(
2538						n,
2539						k,
2540						ParShape::Seq,
2541					),
2542				)),
2543			);
2544			assert!((A_dense.adjoint() * &x - &rhs).norm_max() < 1e-10);
2545		}
2546	}
2547
2548	#[test]
2549	fn test_numeric_lu_simplicial() {
2550		type T = c64;
2551		let (m, n, col_ptr, row_idx, val) = load_mtx::<usize>(
2552			MtxData::from_file(
2553				PathBuf::from(env!("CARGO_MANIFEST_DIR"))
2554					.join("test_data/sparse_lu/YAO.mtx"),
2555			)
2556			.unwrap(),
2557		);
2558		let mut rng = StdRng::seed_from_u64(0);
2559		let mut gen = || T::new(rng.random::<f64>(), rng.random::<f64>());
2560		let val = val.iter().map(|_| gen()).collect::<alloc::vec::Vec<_>>();
2561		let A = SparseColMatRef::<'_, usize, T>::new(
2562			SymbolicSparseColMatRef::new_checked(
2563				m, n, &col_ptr, None, &row_idx,
2564			),
2565			&val,
2566		);
2567		let mut row_perm = vec![0usize; n];
2568		let mut row_perm_inv = vec![0usize; n];
2569		let mut col_perm = vec![0usize; n];
2570		let mut col_perm_inv = vec![0usize; n];
2571		for i in 0..n {
2572			col_perm[i] = i;
2573			col_perm_inv[i] = i;
2574		}
2575		let col_perm =
2576			PermRef::<'_, usize>::new_checked(&col_perm, &col_perm_inv, n);
2577		let mut lu = simplicial::SimplicialLu::<usize, T>::new();
2578		simplicial::factorize_simplicial_numeric_lu(
2579			&mut row_perm,
2580			&mut row_perm_inv,
2581			&mut lu,
2582			A,
2583			col_perm,
2584			MemStack::new(&mut MemBuffer::new(
2585				simplicial::factorize_simplicial_numeric_lu_scratch::<usize, T>(
2586					m, n,
2587				),
2588			)),
2589		)
2590		.unwrap();
2591		let k = 1;
2592		let rhs = Mat::from_fn(n, k, |_, _| gen());
2593		let A_dense = A.to_dense();
2594		let row_perm =
2595			PermRef::<'_, _>::new_checked(&row_perm, &row_perm_inv, m);
2596		{
2597			let mut x = rhs.clone();
2598			lu.solve_in_place_with_conj(
2599				row_perm,
2600				col_perm,
2601				Conj::No,
2602				x.as_mut(),
2603				Par::Seq,
2604				MemStack::new(&mut MemBuffer::new(
2605					simplicial::solve_in_place_scratch::<usize, T>(
2606						n,
2607						k,
2608						ParShape::Seq,
2609					),
2610				)),
2611			);
2612			assert!((&A_dense * &x - &rhs).norm_max() < 1e-10);
2613		}
2614		{
2615			let mut x = rhs.clone();
2616			lu.solve_in_place_with_conj(
2617				row_perm,
2618				col_perm,
2619				Conj::Yes,
2620				x.as_mut(),
2621				Par::Seq,
2622				MemStack::new(&mut MemBuffer::new(
2623					simplicial::solve_in_place_scratch::<usize, T>(
2624						n,
2625						k,
2626						ParShape::Seq,
2627					),
2628				)),
2629			);
2630			assert!((A_dense.conjugate() * &x - &rhs).norm_max() < 1e-10);
2631		}
2632		{
2633			let mut x = rhs.clone();
2634			lu.solve_transpose_in_place_with_conj(
2635				row_perm,
2636				col_perm,
2637				Conj::No,
2638				x.as_mut(),
2639				Par::Seq,
2640				MemStack::new(&mut MemBuffer::new(
2641					simplicial::solve_transpose_in_place_scratch::<usize, T>(
2642						n,
2643						k,
2644						ParShape::Seq,
2645					),
2646				)),
2647			);
2648			assert!((A_dense.transpose() * &x - &rhs).norm_max() < 1e-10);
2649		}
2650		{
2651			let mut x = rhs.clone();
2652			lu.solve_transpose_in_place_with_conj(
2653				row_perm,
2654				col_perm,
2655				Conj::Yes,
2656				x.as_mut(),
2657				Par::Seq,
2658				MemStack::new(&mut MemBuffer::new(
2659					simplicial::solve_transpose_in_place_scratch::<usize, T>(
2660						n,
2661						k,
2662						ParShape::Seq,
2663					),
2664				)),
2665			);
2666			assert!((A_dense.adjoint() * &x - &rhs).norm_max() < 1e-10);
2667		}
2668	}
2669
2670	#[test]
2671	fn test_solver_lu_simplicial() {
2672		type T = c64;
2673		let (m, n, col_ptr, row_idx, val) = load_mtx::<usize>(
2674			MtxData::from_file(
2675				PathBuf::from(env!("CARGO_MANIFEST_DIR"))
2676					.join("test_data/sparse_lu/YAO.mtx"),
2677			)
2678			.unwrap(),
2679		);
2680		let mut rng = StdRng::seed_from_u64(0);
2681		let mut gen = || T::new(rng.random::<f64>(), rng.random::<f64>());
2682		let val = val.iter().map(|_| gen()).collect::<alloc::vec::Vec<_>>();
2683		let A = SparseColMatRef::<'_, usize, T>::new(
2684			SymbolicSparseColMatRef::new_checked(
2685				m, n, &col_ptr, None, &row_idx,
2686			),
2687			&val,
2688		);
2689		let rhs = Mat::<T>::from_fn(m, 6, |_, _| gen());
2690		for supernodal_flop_ratio_threshold in [
2691			SupernodalThreshold::AUTO,
2692			SupernodalThreshold::FORCE_SUPERNODAL,
2693			SupernodalThreshold::FORCE_SIMPLICIAL,
2694		] {
2695			let symbolic = factorize_symbolic_lu(
2696				A.symbolic(),
2697				LuSymbolicParams {
2698					supernodal_flop_ratio_threshold,
2699					..Default::default()
2700				},
2701			)
2702			.unwrap();
2703			let mut numeric = NumericLu::<usize, T>::new();
2704			let lu = symbolic
2705				.factorize_numeric_lu(
2706					&mut numeric,
2707					A,
2708					Par::Seq,
2709					MemStack::new(&mut MemBuffer::new(
2710						symbolic.factorize_numeric_lu_scratch::<T>(
2711							ParShape::Seq,
2712							Default::default(),
2713						),
2714					)),
2715					Default::default(),
2716				)
2717				.unwrap();
2718			{
2719				let mut x = rhs.clone();
2720				lu.solve_in_place_with_conj(
2721					crate::Conj::No,
2722					x.as_mut(),
2723					Par::Seq,
2724					MemStack::new(&mut MemBuffer::new(
2725						symbolic
2726							.solve_in_place_scratch::<T>(rhs.ncols(), ParShape::Seq),
2727					)),
2728				);
2729				let linsolve_diff = A * &x - &rhs;
2730				assert!(linsolve_diff.norm_max() <= 1e-10);
2731			}
2732			{
2733				let mut x = rhs.clone();
2734				lu.solve_in_place_with_conj(
2735					crate::Conj::Yes,
2736					x.as_mut(),
2737					Par::Seq,
2738					MemStack::new(&mut MemBuffer::new(
2739						symbolic
2740							.solve_in_place_scratch::<T>(rhs.ncols(), ParShape::Seq),
2741					)),
2742				);
2743				let linsolve_diff = A.conjugate() * &x - &rhs;
2744				assert!(linsolve_diff.norm_max() <= 1e-10);
2745			}
2746			{
2747				let mut x = rhs.clone();
2748				lu.solve_transpose_in_place_with_conj(
2749					crate::Conj::No,
2750					x.as_mut(),
2751					Par::Seq,
2752					MemStack::new(&mut MemBuffer::new(
2753						symbolic.solve_transpose_in_place_scratch::<T>(
2754							rhs.ncols(),
2755							ParShape::Seq,
2756						),
2757					)),
2758				);
2759				let linsolve_diff = A.transpose() * &x - &rhs;
2760				assert!(linsolve_diff.norm_max() <= 1e-10);
2761			}
2762			{
2763				let mut x = rhs.clone();
2764				lu.solve_transpose_in_place_with_conj(
2765					crate::Conj::Yes,
2766					x.as_mut(),
2767					Par::Seq,
2768					MemStack::new(&mut MemBuffer::new(
2769						symbolic.solve_transpose_in_place_scratch::<T>(
2770							rhs.ncols(),
2771							ParShape::Seq,
2772						),
2773					)),
2774				);
2775				let linsolve_diff = A.adjoint() * &x - &rhs;
2776				assert!(linsolve_diff.norm_max() <= 1e-10);
2777			}
2778		}
2779	}
2780	fn factorize_lu_error<T: ComplexField>(
2781		A: SparseColMatRef<'_, usize, T>,
2782		supernodal_flop_ratio_threshold: SupernodalThreshold,
2783	) -> Result<(), LuError> {
2784		let symbolic = factorize_symbolic_lu(
2785			A.symbolic(),
2786			LuSymbolicParams {
2787				supernodal_flop_ratio_threshold,
2788				..Default::default()
2789			},
2790		)
2791		.unwrap();
2792		let mut numeric = NumericLu::<usize, T>::new();
2793		symbolic
2794			.factorize_numeric_lu(
2795				&mut numeric,
2796				A,
2797				Par::Seq,
2798				MemStack::new(&mut MemBuffer::new(
2799					symbolic.factorize_numeric_lu_scratch::<T>(
2800						ParShape::Seq,
2801						Default::default(),
2802					),
2803				)),
2804				Default::default(),
2805			)
2806			.map(|_| ())
2807	}
2808	fn numeric_singular_index<T: ComplexField>(
2809		n: usize,
2810		entries: &[(usize, usize, f64)],
2811		supernodal_flop_ratio_threshold: SupernodalThreshold,
2812	) -> Option<usize> {
2813		let triplets = entries
2814			.iter()
2815			.map(|&(i, j, v)| Triplet::new(i, j, from_f64::<T>(v)))
2816			.collect::<alloc::vec::Vec<_>>();
2817		let A =
2818			SparseColMat::<usize, T>::try_new_from_triplets(n, n, &triplets)
2819				.unwrap();
2820		match factorize_lu_error(A.as_ref(), supernodal_flop_ratio_threshold) {
2821			Err(LuError::NumericSingular { index }) => Some(index),
2822			Ok(()) => None,
2823			Err(e) => panic!("unexpected error {e:?}"),
2824		}
2825	}
2826	#[test]
2827	fn codeberg_339() {
2828		// upstream sarah-quinones/faer#339: `[[1, 1], [1, 1]]` factored as `Ok`
2829		// and the solve returned NaN; both backends must report the zero pivot,
2830		// which only the second step can meet
2831		let A = [(0, 0, 1.0), (0, 1, 1.0), (1, 0, 1.0), (1, 1, 1.0)];
2832		for threshold in [
2833			SupernodalThreshold::FORCE_SIMPLICIAL,
2834			SupernodalThreshold::FORCE_SUPERNODAL,
2835		] {
2836			assert!(numeric_singular_index::<f64>(2, &A, threshold) == Some(1));
2837			assert!(numeric_singular_index::<f32>(2, &A, threshold) == Some(1));
2838			assert!(numeric_singular_index::<c64>(2, &A, threshold) == Some(1));
2839			assert!(numeric_singular_index::<c32>(2, &A, threshold) == Some(1));
2840		}
2841	}
2842	#[test]
2843	fn numeric_singular_after_an_elimination_step() {
2844		// the second column is a multiple of the first, so one elimination step
2845		// meets a zero pivot. `index` counts steps in the fill-reducing column
2846		// order, as `SymbolicSingular`'s does, so only its range is checked
2847		let A = [
2848			(0, 0, 2.0),
2849			(1, 0, 4.0),
2850			(0, 1, 1.0),
2851			(1, 1, 2.0),
2852			(2, 2, 1.0),
2853			(0, 3, 1.0),
2854			(3, 3, 1.0),
2855			(2, 3, 1.0),
2856		];
2857		// a structurally full matrix that is nonsingular still factors
2858		let B = [
2859			(0, 0, 2.0),
2860			(1, 0, 4.0),
2861			(0, 1, 1.0),
2862			(1, 1, 3.0),
2863			(2, 2, 1.0),
2864			(0, 3, 1.0),
2865			(3, 3, 1.0),
2866			(2, 3, 1.0),
2867		];
2868		for threshold in [
2869			SupernodalThreshold::FORCE_SIMPLICIAL,
2870			SupernodalThreshold::FORCE_SUPERNODAL,
2871		] {
2872			assert!(matches!(
2873				numeric_singular_index::<f64>(4, &A, threshold),
2874				Some(index) if index < 4
2875			));
2876			assert!(numeric_singular_index::<f64>(4, &B, threshold) == None);
2877		}
2878	}
2879	#[test]
2880	fn nonzero_subnormal_pivot_is_not_singular() {
2881		// the modulus of `c64(2^-1074, 0)` underflows to zero, the entry
2882		// itself is not zero
2883		let tiny = f64::from_bits(1);
2884		let triplets = [Triplet::new(0, 0, c64::new(tiny, 0.0))];
2885		let A =
2886			SparseColMat::<usize, c64>::try_new_from_triplets(1, 1, &triplets)
2887				.unwrap();
2888		for threshold in [
2889			SupernodalThreshold::FORCE_SIMPLICIAL,
2890			SupernodalThreshold::FORCE_SUPERNODAL,
2891		] {
2892			assert!(factorize_lu_error(A.as_ref(), threshold).is_ok());
2893		}
2894	}
2895}