1use 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
84pub 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
115pub 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#[derive(Debug, Copy, Clone, PartialEq)]
839pub struct Control {
840 pub dense_row: f64,
842 pub dense_col: f64,
844 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}