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