1use 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
125pub 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 pub dense_row: f64,
850 pub dense_col: f64,
852 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}