Skip to main content

faer/sparse/linalg/
colamd.rs

1//! approximate minimum degree column ordering.
2use crate::assert;
3use crate::internal_prelude_sp::*;
4impl<I: Index> ColamdCol<I> {
5	fn is_dead_principal(&self) -> bool {
6		self.start == I::Signed::truncate(NONE)
7	}
8
9	fn is_dead(&self) -> bool {
10		self.start < I::Signed::truncate(0)
11	}
12
13	fn is_alive(&self) -> bool {
14		!self.is_dead()
15	}
16
17	fn kill_principal(&mut self) {
18		self.start = I::Signed::truncate(NONE);
19	}
20
21	fn kill_non_principal(&mut self) {
22		self.start = I::Signed::truncate(NONE - 1);
23	}
24}
25impl<I: Index> ColamdRow<I> {
26	fn is_dead(&self) -> bool {
27		self.shared2 < I::Signed::truncate(0)
28	}
29
30	fn is_alive(&self) -> bool {
31		!self.is_dead()
32	}
33
34	fn kill(&mut self) {
35		self.shared2 = I::Signed::truncate(NONE);
36	}
37}
38fn clear_mark<I: Index>(
39	tag_mark: I,
40	max_mark: I,
41	row: &mut [ColamdRow<I>],
42) -> I {
43	let I = I::truncate;
44	let SI = I::Signed::truncate;
45	if tag_mark == I(0) || tag_mark >= max_mark {
46		for r in row {
47			if r.is_alive() {
48				r.shared2 = SI(0);
49			}
50		}
51		I(1)
52	} else {
53		tag_mark
54	}
55}
56/// computes the layout of required workspace for computing the colamd ordering
57/// of a matrix
58pub fn order_scratch<I: Index>(
59	nrows: usize,
60	ncols: usize,
61	A_nnz: usize,
62) -> StackReq {
63	let m = nrows;
64	let n = ncols;
65	let n_scratch = StackReq::new::<I>(n);
66	let m_scratch = StackReq::new::<I>(m);
67	let np1_scratch = StackReq::new::<I>(n + 1);
68	let size = StackReq::new::<I>(
69		A_nnz
70			.checked_mul(2)
71			.and_then(|x| x.checked_add(A_nnz / 5))
72			.and_then(|p| p.checked_add(n))
73			.unwrap_or(usize::MAX),
74	);
75	StackReq::or(
76		StackReq::all_of(&[
77			StackReq::new::<ColamdCol<I>>(n + 1),
78			StackReq::new::<ColamdRow<I>>(m + 1),
79			np1_scratch,
80			size,
81		]),
82		StackReq::all_of(&[
83			n_scratch,
84			n_scratch,
85			StackReq::or(
86				StackReq::and(n_scratch, m_scratch),
87				StackReq::all_of(&[n_scratch; 3]),
88			),
89		]),
90	)
91}
92/// computes the approximate minimum degree ordering for reducing the fill-in
93/// during the sparse qr factorization of a matrix with the sparsity pattern of
94/// $A$
95///
96/// # note
97/// allows unsorted matrices
98pub fn order<I: Index>(
99	perm: &mut [I],
100	perm_inv: &mut [I],
101	A: SymbolicSparseColMatRef<'_, I>,
102	control: Control,
103	stack: &mut MemStack,
104) -> Result<(), FaerError> {
105	let m = A.nrows();
106	let n = A.ncols();
107	let I = I::truncate;
108	let SI = I::Signed::truncate;
109	{
110		let (col, stack) = unsafe { stack.make_raw::<ColamdCol<I>>(n + 1) };
111		let (row, stack) = unsafe { stack.make_raw::<ColamdRow<I>>(m + 1) };
112		let nnz = A.compute_nnz();
113		let (p, stack) = unsafe { stack.make_raw::<I>(n + 1) };
114		let size = (2 * nnz)
115			.checked_add(nnz / 5)
116			.and_then(|p| p.checked_add(n));
117		let (new_row_idx, _) = unsafe {
118			stack.make_raw::<I>(size.ok_or(FaerError::IndexOverflow)?)
119		};
120		p[0] = I(0);
121		for j in 0..n {
122			let row_idx = A.row_idx_of_col_raw(j);
123			p[j + 1] = p[j] + I(row_idx.len());
124			new_row_idx[p[j].zx()..p[j + 1].zx()].copy_from_slice(&*row_idx);
125		}
126		let A = new_row_idx;
127		for c in 0..n {
128			col[c] = ColamdCol::<I> {
129				start: p[c].to_signed(),
130				length: p[c + 1].to_signed() - p[c].to_signed(),
131				shared1: SI(1),
132				shared2: SI(0),
133				shared3: SI(NONE),
134				shared4: SI(NONE),
135			};
136		}
137		col[n] = ColamdCol::<I> {
138			start: SI(NONE),
139			length: SI(NONE),
140			shared1: SI(NONE),
141			shared2: SI(NONE),
142			shared3: SI(NONE),
143			shared4: SI(NONE),
144		};
145		for r in 0..m {
146			row[r] = ColamdRow::<I> {
147				start: SI(0),
148				length: SI(0),
149				shared1: SI(NONE),
150				shared2: SI(NONE),
151			};
152		}
153		row[m] = ColamdRow::<I> {
154			start: SI(NONE),
155			length: SI(NONE),
156			shared1: SI(NONE),
157			shared2: SI(NONE),
158		};
159		let mut jumbled = false;
160		for c in 0..n {
161			let mut last_row = SI(NONE);
162			let mut cp = p[c].zx();
163			let cp_end = p[c + 1].zx();
164			while cp < cp_end {
165				let r = A[cp].zx();
166				cp += 1;
167				if SI(r) <= last_row || row[r].shared2 == SI(c) {
168					jumbled = true;
169				}
170				if row[r].shared2 != SI(c) {
171					row[r].length += SI(1);
172				} else {
173					col[c].length -= SI(1);
174				}
175				row[r].shared2 = SI(c);
176				last_row = SI(r);
177			}
178		}
179		row[0].start = p[n].to_signed();
180		row[0].shared1 = row[0].start;
181		row[0].shared2 = -SI(1);
182		for r in 1..m {
183			row[r].start = row[r - 1].start + row[r - 1].length;
184			row[r].shared1 = row[r].start;
185			row[r].shared2 = -SI(1);
186		}
187		if jumbled {
188			for c in 0..n {
189				let mut cp = p[c].zx();
190				let cp_end = p[c + 1].zx();
191				while cp < cp_end {
192					let r = A[cp].zx();
193					cp += 1;
194					if row[r].shared2 != SI(c) {
195						A[row[r].shared1.zx()] = I(c);
196						row[r].shared1 += SI(1);
197						row[r].shared2 = SI(c);
198					}
199				}
200			}
201		} else {
202			for c in 0..n {
203				let mut cp = p[c].zx();
204				let cp_end = p[c + 1].zx();
205				while cp < cp_end {
206					let r = A[cp].zx();
207					cp += 1;
208					A[row[r].shared1.zx()] = I(c);
209					row[r].shared1 += SI(1);
210				}
211			}
212		}
213		for r in 0..m {
214			row[r].shared2 = SI(0);
215			row[r].shared1 = row[r].length;
216		}
217		if jumbled {
218			col[0].start = SI(0);
219			p[0] = I::from_signed(col[0].start);
220			for c in 1..n {
221				col[c].start = col[c - 1].start + col[c - 1].length;
222				p[c] = I::from_signed(col[c].start);
223			}
224			for r in 0..m {
225				let mut rp = row[r].start.zx();
226				let rp_end = rp + row[r].length.zx();
227				while rp < rp_end {
228					A[p[rp].zx()] = I(r);
229					p[rp] += I(1);
230					rp += 1;
231				}
232			}
233		}
234		let dense_row_count = if control.dense_row < 0.0 {
235			n - 1
236		} else {
237			Ord::max(16, (control.dense_row * n as f64) as usize)
238		};
239		let dense_col_count = if control.dense_col < 0.0 {
240			m - 1
241		} else {
242			Ord::max(16, (control.dense_col * Ord::min(m, n) as f64) as usize)
243		};
244		let mut max_deg = 0;
245		let mut ncol2 = n;
246		// let nrow2 = m;
247		// let _ = nrow2;
248		let head = &mut *p;
249		for c in (0..n).rev() {
250			let deg = col[c].length;
251			if deg == SI(0) {
252				ncol2 -= 1;
253				col[c].shared2 = SI(ncol2);
254				col[c].kill_principal();
255			}
256		}
257		for c in (0..n).rev() {
258			if col[c].is_dead() {
259				continue;
260			}
261			let deg = col[c].length;
262			if deg.zx() > dense_col_count {
263				ncol2 -= 1;
264				col[c].shared2 = SI(ncol2);
265				let mut cp = col[c].start.zx();
266				let cp_end = cp + col[c].length.zx();
267				while cp < cp_end {
268					row[A[cp].zx()].shared1 -= SI(1);
269					cp += 1;
270				}
271				col[c].kill_principal();
272			}
273		}
274		for r in 0..m {
275			let deg = row[r].shared1.zx();
276			assert!(deg <= n);
277			if deg > dense_row_count || deg == 0 {
278				row[r].kill();
279				// nrow2 -= 1;
280			} else {
281				max_deg = Ord::max(deg, max_deg);
282			}
283		}
284		for c in (0..n).rev() {
285			if col[c].is_dead() {
286				continue;
287			}
288			let mut score = 0;
289			let mut cp = col[c].start.zx();
290			let mut new_cp = cp;
291			let cp_end = cp + col[c].length.zx();
292			while cp < cp_end {
293				let r = A[cp].zx();
294				cp += 1;
295				if row[r].is_dead() {
296					continue;
297				}
298				A[new_cp] = I(r);
299				new_cp += 1;
300				score += row[r].shared1.zx() - 1;
301				score = Ord::min(score, n);
302			}
303			let col_length = new_cp - col[c].start.zx();
304			if col_length == 0 {
305				ncol2 -= 1;
306				col[c].shared2 = SI(ncol2);
307				col[c].kill_principal();
308			} else {
309				assert!(score <= n);
310				col[c].length = SI(col_length);
311				col[c].shared2 = SI(score);
312			}
313		}
314		head.fill(I(NONE));
315		let mut min_score = n;
316		for c in (0..n).rev() {
317			if col[c].is_alive() {
318				let score = col[c].shared2.zx();
319				assert!(min_score <= n);
320				assert!(score <= n);
321				assert!(head[score].to_signed() >= SI(NONE));
322				let next_col = head[score];
323				col[c].shared3 = SI(NONE);
324				col[c].shared4 = next_col.to_signed();
325				if next_col != I(NONE) {
326					col[next_col.zx()].shared3 = SI(c);
327				}
328				head[score] = I(c);
329				min_score = Ord::min(score, min_score);
330			}
331		}
332		let max_mark = I::from_signed(I::Signed::MAX) - I(n);
333		let mut tag_mark = clear_mark(I(0), max_mark, row);
334		let mut min_score = 0;
335		let mut pfree = 2 * nnz;
336		let mut k = 0;
337		while k < ncol2 {
338			assert!(min_score <= n);
339			assert!(head[min_score].to_signed() >= SI(NONE));
340			while head[min_score] == I(NONE) && min_score < n {
341				min_score += 1;
342			}
343			let pivot_col = head[min_score].zx();
344			let mut next_col = col[pivot_col].shared4;
345			head[min_score] = I::from_signed(next_col);
346			if next_col != SI(NONE) {
347				col[next_col.zx()].shared3 = SI(NONE);
348			}
349			assert!(!col[pivot_col].is_dead());
350			let pivot_col_score = col[pivot_col].shared2;
351			col[pivot_col].shared2 = SI(k);
352			let pivot_col_thickness = col[pivot_col].shared1;
353			assert!(pivot_col_thickness > SI(0));
354			k += pivot_col_thickness.zx();
355			let needed_memory = Ord::min(pivot_col_score, SI(n - k));
356			if pfree as isize + needed_memory.sx() as isize >= A.len() as isize
357			{
358				pfree = garbage_collection(row, col, A, pfree);
359				assert!(
360					(pfree as isize + needed_memory.sx() as isize)
361						< A.len() as isize
362				);
363				tag_mark = clear_mark(I(0), max_mark, row);
364			}
365			let pivot_row_start = pfree;
366			let mut pivot_row_degree = 0;
367			col[pivot_col].shared1 = -pivot_col_thickness;
368			let mut cp = col[pivot_col].start.zx();
369			let cp_end = cp + col[pivot_col].length.zx();
370			while cp < cp_end {
371				let r = A[cp].zx();
372				cp += 1;
373				if row[r].is_alive() {
374					let mut rp = row[r].start.zx();
375					let rp_end = rp + row[r].length.zx();
376					while rp < rp_end {
377						let c = A[rp].zx();
378						rp += 1;
379						let col_thickness = col[c].shared1;
380						if col_thickness > SI(0) && col[c].is_alive() {
381							col[c].shared1 = -col_thickness;
382							A[pfree] = I(c);
383							pfree += 1;
384							pivot_row_degree += col_thickness.zx();
385						}
386					}
387				}
388			}
389			col[pivot_col].shared1 = pivot_col_thickness;
390			max_deg = Ord::max(max_deg, pivot_row_degree);
391			let mut cp = col[pivot_col].start.zx();
392			let cp_end = cp + col[pivot_col].length.zx();
393			while cp < cp_end {
394				let r = A[cp].zx();
395				cp += 1;
396				row[r].kill();
397			}
398			let pivot_row_length = pfree - pivot_row_start;
399			let pivot_row = if pivot_row_length > 0 {
400				A[col[pivot_col].start.zx()]
401			} else {
402				I(NONE)
403			};
404			let mut rp = pivot_row_start;
405			let rp_end = rp + pivot_row_length;
406			while rp < rp_end {
407				let c = A[rp].zx();
408				rp += 1;
409				assert!(col[c].is_alive());
410				let col_thickness = -col[c].shared1;
411				assert!(col_thickness > SI(0));
412				col[c].shared1 = col_thickness;
413				let cur_score = col[c].shared2.zx();
414				let prev_col = col[c].shared3;
415				let next_col = col[c].shared4;
416				assert!(cur_score <= n);
417				if prev_col == SI(NONE) {
418					head[cur_score] = I::from_signed(next_col);
419				} else {
420					col[prev_col.zx()].shared4 = next_col;
421				}
422				if next_col != SI(NONE) {
423					col[next_col.zx()].shared3 = prev_col;
424				}
425				let mut cp = col[c].start.zx();
426				let cp_end = cp + col[c].length.zx();
427				while cp < cp_end {
428					let r = A[cp].zx();
429					cp += 1;
430					let row_mark = row[r].shared2;
431					if row[r].is_dead() {
432						continue;
433					}
434					assert!(I(r) != pivot_row);
435					let mut set_difference = row_mark - tag_mark.to_signed();
436					if set_difference < SI(0) {
437						assert!(row[r].shared1 <= SI(max_deg));
438						set_difference = row[r].shared1;
439					}
440					set_difference -= col_thickness;
441					assert!(set_difference >= SI(0));
442					if set_difference == SI(0) && control.aggressive {
443						row[r].kill();
444					} else {
445						row[r].shared2 = set_difference + tag_mark.to_signed();
446					}
447				}
448			}
449			let mut rp = pivot_row_start;
450			let rp_end = rp + pivot_row_length;
451			while rp < rp_end {
452				let c = A[rp].zx();
453				rp += 1;
454				assert!(col[c].is_alive());
455				let mut hash = 0;
456				let mut cur_score = 0;
457				let mut cp = col[c].start.zx();
458				let mut new_cp = cp;
459				let cp_end = cp + col[c].length.zx();
460				while cp < cp_end {
461					let r = A[cp].zx();
462					cp += 1;
463					let row_mark = row[r].shared2;
464					if row[r].is_dead() {
465						continue;
466					}
467					assert!(row_mark >= tag_mark.to_signed());
468					A[new_cp] = I(r);
469					new_cp += 1;
470					hash += r;
471					cur_score += (row_mark - tag_mark.to_signed()).zx();
472					cur_score = Ord::min(cur_score, n);
473				}
474				col[c].length = SI(new_cp - col[c].start.zx());
475				if col[c].length.zx() == 0 {
476					col[c].kill_principal();
477					pivot_row_degree -= col[c].shared1.zx();
478					col[c].shared2 = SI(k);
479					k += col[c].shared1.zx();
480				} else {
481					col[c].shared2 = SI(cur_score);
482					hash %= n + 1;
483					let head_column = head[hash];
484					let first_col;
485					if head_column.to_signed() > SI(NONE) {
486						first_col = col[head_column.zx()].shared3;
487						col[head_column.zx()].shared3 = SI(c);
488					} else {
489						first_col = -(head_column.to_signed() + SI(2));
490						head[hash] = I::from_signed(-SI(c + 2));
491					}
492					col[c].shared4 = first_col;
493					col[c].shared3 = SI(hash);
494					assert!(col[c].is_alive());
495				}
496			}
497			detect_super_cols(col, A, head, pivot_row_start, pivot_row_length);
498			col[pivot_col].kill_principal();
499			tag_mark = clear_mark(tag_mark + I(max_deg) + I(1), max_mark, row);
500			let mut rp = pivot_row_start;
501			let mut new_rp = rp;
502			let rp_end = rp + pivot_row_length;
503			while rp < rp_end {
504				let c = A[rp].zx();
505				rp += 1;
506				if col[c].is_dead() {
507					continue;
508				}
509				A[new_rp] = I(c);
510				new_rp += 1;
511				A[(col[c].start + col[c].length).zx()] = pivot_row;
512				col[c].length += SI(1);
513				let mut cur_score = col[c].shared2.zx() + pivot_row_degree;
514				let max_score = n - k - col[c].shared1.zx();
515				cur_score -= col[c].shared1.zx();
516				cur_score = Ord::min(cur_score, max_score);
517				col[c].shared2 = SI(cur_score);
518				next_col = head[cur_score].to_signed();
519				col[c].shared4 = next_col;
520				col[c].shared3 = SI(NONE);
521				if next_col != SI(NONE) {
522					col[next_col.zx()].shared3 = SI(c);
523				}
524				head[cur_score] = I(c);
525				min_score = Ord::min(min_score, cur_score);
526			}
527			if pivot_row_degree > 0 {
528				let pivot_row = pivot_row.zx();
529				row[pivot_row].start = SI(pivot_row_start);
530				row[pivot_row].length = SI(new_rp - pivot_row_start);
531				row[pivot_row].shared1 = SI(pivot_row_degree);
532				row[pivot_row].shared2 = SI(0);
533			}
534		}
535		for i in 0..n {
536			assert!(col[i].is_dead());
537			if !col[i].is_dead_principal() && col[i].shared2 == SI(NONE) {
538				let mut parent = i;
539				loop {
540					parent = col[parent].shared1.zx();
541					if col[parent].is_dead_principal() {
542						break;
543					}
544				}
545				let mut c = i;
546				let mut order = col[parent].shared2.zx();
547				loop {
548					assert!(col[c].shared2 == SI(NONE));
549					col[c].shared2 = SI(order);
550					order += 1;
551					col[c].shared1 = SI(parent);
552					c = col[c].shared1.zx();
553					if col[c].shared2 != SI(NONE) {
554						break;
555					}
556				}
557				col[parent].shared2 = SI(order);
558			}
559		}
560		for c in 0..n {
561			perm[col[c].shared2.zx()] = I(c);
562		}
563		for c in 0..n {
564			perm_inv[perm[c].zx()] = I(c);
565		}
566	}
567	let mut etree = alloc::vec![I(0); n];
568	let mut post = alloc::vec![I(0); n];
569	let etree = super::qr::col_etree::<I>(
570		A,
571		Some(PermRef::<'_, I>::new_checked(perm, perm_inv, n)),
572		&mut etree,
573		stack,
574	);
575	super::qr::postorder(&mut post, etree, stack);
576	for i in 0..n {
577		perm[post[i].zx()] = I(i);
578	}
579	for i in 0..n {
580		perm_inv[i] = perm[perm_inv[i].zx()];
581	}
582	for i in 0..n {
583		perm[perm_inv[i].zx()] = I(i);
584	}
585	Ok(())
586}
587fn detect_super_cols<I: Index>(
588	col: &mut [ColamdCol<I>],
589	A: &mut [I],
590	head: &mut [I],
591	row_start: usize,
592	row_length: usize,
593) {
594	let I = I::truncate;
595	let SI = I::Signed::truncate;
596	let mut rp = row_start;
597	let rp_end = rp + row_length;
598	while rp < rp_end {
599		let c = A[rp].zx();
600		rp += 1;
601		if col[c].is_dead() {
602			continue;
603		}
604		let hash = col[c].shared3.zx();
605		let head_column = head[hash].to_signed();
606		let first_col = if head_column > SI(NONE) {
607			col[head_column.zx()].shared3
608		} else {
609			-(head_column + SI(2))
610		};
611		let mut super_c_ = first_col;
612		while super_c_ != SI(NONE) {
613			let super_c = super_c_.zx();
614			let length = col[super_c].length;
615			let mut prev_c = super_c;
616			let mut c_ = col[super_c].shared4;
617			while c_ != SI(NONE) {
618				let c = c_.zx();
619				if col[c].length != length
620					|| col[c].shared2 != col[super_c].shared2
621				{
622					prev_c = c;
623					c_ = col[c].shared4;
624					continue;
625				}
626				let mut cp1 = col[super_c].start.zx();
627				let mut cp2 = col[c].start.zx();
628				let mut i = 0;
629				while i < length.zx() {
630					if A[cp1] != A[cp2] {
631						break;
632					}
633					cp1 += 1;
634					cp2 += 1;
635					i += 1;
636				}
637				if i != length.zx() {
638					prev_c = c;
639					c_ = col[c].shared4;
640					continue;
641				}
642				col[super_c].shared1 += col[c].shared1;
643				col[c].shared1 = SI(super_c);
644				col[c].kill_non_principal();
645				col[c].shared2 = SI(NONE);
646				col[prev_c].shared4 = col[c].shared4;
647				c_ = col[c].shared4;
648			}
649			super_c_ = col[super_c].shared4;
650		}
651		if head_column > SI(NONE) {
652			col[head_column.zx()].shared3 = SI(NONE);
653		} else {
654			head[hash] = I(NONE);
655		}
656	}
657}
658fn garbage_collection<I: Index>(
659	row: &mut [ColamdRow<I>],
660	col: &mut [ColamdCol<I>],
661	A: &mut [I],
662	pfree: usize,
663) -> usize {
664	let I = I::truncate;
665	let SI = I::Signed::truncate;
666	let mut pdest = 0usize;
667	let m = row.len() - 1;
668	let n = col.len() - 1;
669	for c in 0..n {
670		if !col[c].is_dead() {
671			let mut psrc = col[c].start.zx();
672			col[c].start = SI(pdest);
673			let length = col[c].length.zx();
674			for _ in 0..length {
675				let r = A[psrc].zx();
676				psrc += 1;
677				if !row[r].is_dead() {
678					A[pdest] = I(r);
679					pdest += 1;
680				}
681			}
682			col[c].length = SI(pdest) - col[c].start;
683		}
684	}
685	for r in 0..m {
686		if row[r].is_dead() || row[r].length == SI(0) {
687			row[r].kill();
688		} else {
689			let psrc = row[r].start.zx();
690			row[r].shared2 = A[psrc].to_signed();
691			A[psrc] = !I(r);
692		}
693	}
694	let mut psrc = pdest;
695	while psrc < pfree {
696		let psrc_ = psrc;
697		psrc += 1;
698		if A[psrc_].to_signed() < SI(0) {
699			psrc -= 1;
700			let r = (!A[psrc]).zx();
701			A[psrc] = I::from_signed(row[r].shared2);
702			row[r].start = SI(pdest);
703			let length = row[r].length.zx();
704			for _ in 0..length {
705				let c = A[psrc].zx();
706				psrc += 1;
707				if !col[c].is_dead() {
708					A[pdest] = I(c);
709					pdest += 1;
710				}
711			}
712			row[r].length = SI(pdest) - row[r].start;
713		}
714	}
715	pdest
716}
717#[derive(Copy, Clone, Debug)]
718#[repr(C)]
719struct ColamdRow<I: Index> {
720	start: I::Signed,
721	length: I::Signed,
722	shared1: I::Signed,
723	shared2: I::Signed,
724}
725#[derive(Copy, Clone, Debug)]
726#[repr(C)]
727struct ColamdCol<I: Index> {
728	start: I::Signed,
729	length: I::Signed,
730	shared1: I::Signed,
731	shared2: I::Signed,
732	shared3: I::Signed,
733	shared4: I::Signed,
734}
735unsafe impl<I: Index> bytemuck::Zeroable for ColamdCol<I> {}
736unsafe impl<I: Index> bytemuck::Pod for ColamdCol<I> {}
737unsafe impl<I: Index> bytemuck::Zeroable for ColamdRow<I> {}
738unsafe impl<I: Index> bytemuck::Pod for ColamdRow<I> {}
739/// tuning parameters for the amd implementation
740#[derive(Debug, Copy, Clone, PartialEq)]
741pub struct Control {
742	/// "dense" if degree > dense_row * sqrt(ncols)
743	pub dense_row: f64,
744	/// "dense" if degree > dense_col * sqrt(min(nrows, ncols))
745	pub dense_col: f64,
746	/// do aggressive absorption
747	pub aggressive: bool,
748}
749impl Default for Control {
750	#[inline]
751	fn default() -> Self {
752		Self {
753			dense_row: 0.5,
754			dense_col: 0.5,
755			aggressive: true,
756		}
757	}
758}