faer/sparse/linalg/
colamd.rs

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