1use crate::factor::count_f64;
97use crate::perm::idx32;
98use crate::qgraph::{self, Input};
99use crate::real::lt;
100use crate::{Pattern, Perm, SparseError};
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum OrderingTarget {
105 Symmetric,
108 Columns,
111}
112
113pub trait Ordering {
116 fn order(&self, pattern: &Pattern, target: OrderingTarget) -> Result<Perm, SparseError>;
119 fn name(&self) -> &'static str;
121}
122
123#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
125pub struct Natural;
126
127#[derive(Clone, Copy, Debug, PartialEq)]
132pub struct Amd {
133 pub dense: Option<f64>,
136 pub aggressive: bool,
138}
139
140impl Default for Amd {
141 fn default() -> Self {
142 Self {
143 dense: None,
144 aggressive: true,
145 }
146 }
147}
148
149#[derive(Clone, Copy, Debug, Default, PartialEq)]
153pub struct Colamd {
154 pub dense_row: Option<f64>,
157 pub dense_col: Option<f64>,
161}
162
163const DENSE_DEFAULT: f64 = 10.0;
165
166const DENSE_MIN: usize = 16;
168
169fn dense_threshold(alpha: Option<f64>, big_n: usize) -> usize {
171 let a = alpha.unwrap_or(DENSE_DEFAULT);
172 if lt(a, 0.0) {
173 return usize::MAX;
174 }
175 floor_usize(a * count_f64(big_n).sqrt()).max(DENSE_MIN)
176}
177
178#[inline]
180#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn floor_usize(x: f64) -> usize {
182 x as usize
183}
184
185fn square(pattern: &Pattern) -> Result<usize, SparseError> {
187 let n = pattern.n_cols();
188 if pattern.n_rows == n {
189 Ok(n)
190 } else {
191 Err(SparseError::Dimension {
192 what: "pattern (a symmetric ordering needs a square one)",
193 expected: n,
194 found: pattern.n_rows,
195 })
196 }
197}
198
199fn adjacency(g: &Pattern) -> Vec<Vec<u32>> {
201 (0..g.n_cols())
202 .map(|j| {
203 g.col(j)
204 .iter()
205 .copied()
206 .filter(|&i| i as usize != j)
207 .collect()
208 })
209 .collect()
210}
211
212fn sym_adjacency(s: &Pattern) -> Vec<Vec<u32>> {
216 let t = s.transpose();
217 (0..s.n_cols())
218 .map(|j| {
219 let (a, b) = (s.col(j), t.col(j));
220 let jj = idx32(j);
221 let mut out = Vec::with_capacity(a.len() + b.len());
222 let (mut p, mut q) = (0, 0);
223 while p < a.len() && q < b.len() {
224 let (x, y) = (a[p], b[q]);
225 let next = x.min(y);
226 p += usize::from(x == next);
227 q += usize::from(y == next);
228 if next != jj {
229 out.push(next);
230 }
231 }
232 out.extend(a[p..].iter().chain(&b[q..]).copied().filter(|&i| i != jj));
233 out
234 })
235 .collect()
236}
237
238fn finish(n: usize, mut fwd: Vec<u32>, groups: &[&[bool]]) -> Perm {
240 for g in groups {
241 fwd.extend((0..n).filter(|&j| g[j]).map(idx32));
242 }
243 debug_assert_eq!(fwd.len(), n);
244 let p = Perm::from_fwd(fwd).expect("(O1): the ordering is a permutation");
245 debug_assert_eq!(p.check_invariants(), Ok(()));
246 p
247}
248
249impl Ordering for Natural {
250 fn order(&self, pattern: &Pattern, target: OrderingTarget) -> Result<Perm, SparseError> {
251 pattern.check_invariants()?;
252 let n = match target {
253 OrderingTarget::Symmetric => square(pattern)?,
254 OrderingTarget::Columns => pattern.n_cols(),
255 };
256 Ok(Perm::identity(n))
257 }
258 fn name(&self) -> &'static str {
259 "natural"
260 }
261}
262
263impl Ordering for Amd {
264 fn order(&self, pattern: &Pattern, target: OrderingTarget) -> Result<Perm, SparseError> {
265 pattern.check_invariants()?;
266 let adj = match target {
268 OrderingTarget::Symmetric => {
269 square(pattern)?;
270 sym_adjacency(pattern)
271 }
272 OrderingTarget::Columns => adjacency(&pattern.ata()),
273 };
274 let n = adj.len();
275 let delta = dense_threshold(self.dense, n);
277 let excluded: Vec<bool> = adj.iter().map(|a| a.len() > delta).collect();
278 let fwd = qgraph::order(Input {
280 n,
281 adj,
282 elems: Vec::new(),
283 excluded: excluded.clone(),
284 aggressive: self.aggressive,
285 previous_degree: true,
286 column_order: false,
287 });
288 Ok(finish(n, fwd, &[excluded.as_slice()]))
290 }
291 fn name(&self) -> &'static str {
292 "amd"
293 }
294}
295
296impl Ordering for Colamd {
297 fn order(&self, pattern: &Pattern, target: OrderingTarget) -> Result<Perm, SparseError> {
298 pattern.check_invariants()?;
299 match target {
300 OrderingTarget::Columns => Ok(self.columns(pattern)),
301 OrderingTarget::Symmetric => {
302 square(pattern)?;
303 Ok(self.symmetric(&pattern.symmetric()?))
304 }
305 }
306 }
307 fn name(&self) -> &'static str {
308 "colamd"
309 }
310}
311
312impl Colamd {
313 fn columns(self, s: &Pattern) -> Perm {
315 let (m, n) = (s.n_rows, s.n_cols());
316 let kc = dense_threshold(self.dense_col, m.min(n));
318 let dense: Vec<bool> = (0..n).map(|j| s.col(j).len() > kc).collect();
319 let t = s.transpose();
321 let kr = dense_threshold(self.dense_row, n);
322 let mut elems = Vec::with_capacity(m);
323 let mut has_row = vec![false; n];
324 for i in 0..m {
325 let row: Vec<u32> = t
326 .col(i)
327 .iter()
328 .copied()
329 .filter(|&j| !dense[j as usize])
330 .collect();
331 if row.is_empty() || row.len() > kr {
332 continue;
333 }
334 for &j in &row {
335 has_row[j as usize] = true;
336 }
337 elems.push(row);
338 }
339 let null: Vec<bool> = (0..n).map(|j| !dense[j] && !has_row[j]).collect();
341 let excluded: Vec<bool> = (0..n).map(|j| dense[j] || null[j]).collect();
342 let fwd = qgraph::order(Input {
344 n,
345 adj: vec![Vec::new(); n],
346 elems,
347 excluded,
348 aggressive: true,
349 previous_degree: false,
350 column_order: true,
351 });
352 finish(n, fwd, &[null.as_slice(), dense.as_slice()])
354 }
355
356 fn symmetric(self, g: &Pattern) -> Perm {
358 let n = g.n_cols();
359 let adj = adjacency(g);
360 let kc = dense_threshold(self.dense_col, n);
361 let dense: Vec<bool> = adj.iter().map(|a| a.len() > kc).collect();
362 let mut elems = Vec::new();
363 for (j, a) in adj.iter().enumerate() {
364 if dense[j] {
365 continue;
366 }
367 for &i in a {
368 if (i as usize) < j && !dense[i as usize] {
369 elems.push(vec![i, idx32(j)]);
370 }
371 }
372 }
373 let fwd = qgraph::order(Input {
374 n,
375 adj: vec![Vec::new(); n],
376 elems,
377 excluded: dense.clone(),
378 aggressive: true,
379 previous_degree: false,
380 column_order: true,
381 });
382 finish(n, fwd, &[dense.as_slice()])
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use proptest::prelude::*;
390
391 struct Rng(u64);
393 impl Rng {
394 fn new(seed: u64) -> Self {
395 Self(seed | 1)
396 }
397 fn below(&mut self, n: usize) -> usize {
398 self.0 ^= self.0 >> 12;
399 self.0 ^= self.0 << 25;
400 self.0 ^= self.0 >> 27;
401 usize::try_from(self.0.wrapping_mul(0x2545_F491_4F6C_DD1D) % (n as u64)).unwrap()
402 }
403 fn shuffle(&mut self, v: &mut [u32]) {
404 for i in (1..v.len()).rev() {
405 let j = self.below(i + 1);
406 v.swap(i, j);
407 }
408 }
409 }
410
411 fn pat(m: usize, n: usize, e: &[(usize, usize)]) -> Pattern {
413 let mut cols: Vec<Vec<u32>> = vec![Vec::new(); n];
414 for &(i, j) in e {
415 cols[j].push(idx32(i));
416 }
417 let mut col_ptr = vec![0];
418 let mut row_idx = Vec::new();
419 for mut c in cols {
420 c.sort_unstable();
421 c.dedup();
422 row_idx.extend(c);
423 col_ptr.push(row_idx.len());
424 }
425 Pattern::from_raw_parts(m, col_ptr, row_idx).unwrap()
426 }
427
428 fn graph(n: usize, edges: &[(usize, usize)], label: &[u32]) -> Pattern {
431 let mut e: Vec<(usize, usize)> = (0..n).map(|i| (i, i)).collect();
432 for &(a, b) in edges {
433 let (a, b) = (label[a] as usize, label[b] as usize);
434 e.push((a, b));
435 e.push((b, a));
436 }
437 pat(n, n, &e)
438 }
439
440 fn random_label(rng: &mut Rng, n: usize) -> Vec<u32> {
441 let mut l: Vec<u32> = (0..n).map(idx32).collect();
442 rng.shuffle(&mut l);
443 l
444 }
445
446 fn tree_edges(rng: &mut Rng, n: usize) -> Vec<(usize, usize)> {
448 (1..n).map(|i| (i, rng.below(i))).collect()
449 }
450
451 fn grid_edges(k: usize) -> Vec<(usize, usize)> {
453 let mut e = Vec::new();
454 for r in 0..k {
455 for c in 0..k {
456 let v = r * k + c;
457 if c + 1 < k {
458 e.push((v, v + 1));
459 }
460 if r + 1 < k {
461 e.push((v, v + k));
462 }
463 }
464 }
465 e
466 }
467
468 fn incidence(n: usize, edges: &[(usize, usize)], label: &[u32]) -> Pattern {
470 let mut e = Vec::new();
471 for (r, &(a, b)) in edges.iter().enumerate() {
472 e.push((r, label[a] as usize));
473 e.push((r, label[b] as usize));
474 }
475 pat(edges.len().max(1), n, &e)
476 }
477
478 fn fill(s: &Pattern, p: &Perm) -> usize {
479 s.cholesky_nnz(p).unwrap()
480 }
481
482 fn exact_minimum_degree(s: &Pattern) -> Perm {
489 let n = s.n_cols();
490 let g = s.symmetric().unwrap();
491 let mut adj: Vec<Vec<bool>> = vec![vec![false; n]; n];
492 for (j, row) in adj.iter_mut().enumerate() {
494 for &i in g.col(j) {
495 if i as usize != j {
496 row[i as usize] = true;
497 }
498 }
499 }
500 let mut alive = vec![true; n];
501 let mut fwd = Vec::with_capacity(n);
502 for _ in 0..n {
503 let degree = |v: usize| (0..n).filter(|&w| alive[w] && adj[v][w]).count();
504 let p = (0..n)
505 .filter(|&v| alive[v])
506 .min_by_key(|&v| (degree(v), v))
507 .unwrap();
508 let nb: Vec<usize> = (0..n).filter(|&w| alive[w] && adj[p][w]).collect();
509 for &a in &nb {
510 for &b in &nb {
511 if a != b {
512 adj[a][b] = true;
513 }
514 }
515 }
516 alive[p] = false;
517 fwd.push(idx32(p));
518 }
519 Perm::from_fwd(fwd).unwrap()
520 }
521
522 fn optimal_fill(s: &Pattern) -> usize {
524 fn rec(k: usize, v: &mut Vec<u32>, s: &Pattern, best: &mut usize) {
525 if k == v.len() {
526 let f = s.cholesky_nnz(&Perm::from_fwd(v.clone()).unwrap()).unwrap();
527 *best = (*best).min(f);
528 return;
529 }
530 for i in k..v.len() {
531 v.swap(k, i);
532 rec(k + 1, v, s, best);
533 v.swap(k, i);
534 }
535 }
536 let mut v: Vec<u32> = (0..s.n_cols()).map(idx32).collect();
537 let mut best = usize::MAX;
538 rec(0, &mut v, s, &mut best);
539 best
540 }
541
542 fn random_graph(rng: &mut Rng, n: usize, pct: usize) -> Pattern {
543 let mut e = Vec::new();
544 for a in 0..n {
545 for b in a + 1..n {
546 if rng.below(100) < pct {
547 e.push((a, b));
548 }
549 }
550 }
551 let id: Vec<u32> = (0..n).map(idx32).collect();
552 graph(n, &e, &id)
553 }
554
555 #[test]
560 fn natural_and_errors() {
561 let s = pat(2, 3, &[(0, 0), (1, 2)]);
562 assert!(
563 Natural
564 .order(&s, OrderingTarget::Columns)
565 .unwrap()
566 .is_identity()
567 );
568 assert!(Natural.order(&s, OrderingTarget::Symmetric).is_err());
569 assert!(Amd::default().order(&s, OrderingTarget::Symmetric).is_err());
570 assert!(
571 Colamd::default()
572 .order(&s, OrderingTarget::Symmetric)
573 .is_err()
574 );
575 assert_eq!(
576 Amd::default()
577 .order(&s, OrderingTarget::Columns)
578 .unwrap()
579 .len(),
580 3
581 );
582 assert_eq!(
583 Colamd::default()
584 .order(&s, OrderingTarget::Columns)
585 .unwrap()
586 .len(),
587 3
588 );
589 let empty = Pattern::empty(0, 0);
590 for o in [
591 &Natural as &dyn Ordering,
592 &Amd::default(),
593 &Colamd::default(),
594 ] {
595 assert!(
596 o.order(&empty, OrderingTarget::Symmetric)
597 .unwrap()
598 .is_empty()
599 );
600 assert_ne!(o.name(), "");
601 }
602 assert_eq!(dense_threshold(None, 100), 100);
603 assert_eq!(dense_threshold(Some(1.0), 4), DENSE_MIN);
604 assert_eq!(dense_threshold(Some(-1.0), 4), usize::MAX);
605 }
606
607 #[test]
611 fn long_path_restarts_the_flag_and_stays_perfect() {
612 let n = 100_000;
613 let edges: Vec<(usize, usize)> = (1..n).map(|i| (i, i - 1)).collect();
614 let s = pat(n, n, &edges).symmetric().unwrap();
615 for o in [&Amd::default() as &dyn Ordering, &Colamd::default()] {
616 let p = o.order(&s, OrderingTarget::Symmetric).unwrap();
617 assert_eq!(s.cholesky_nnz(&p).unwrap(), 2 * n - 1, "{}", o.name());
618 }
619 }
620
621 #[test]
626 fn ex_5_10_1_arrowhead() {
627 for n in [8_usize, 30, 400] {
628 let edges: Vec<(usize, usize)> = (1..n).map(|i| (0, i)).collect();
629 let id: Vec<u32> = (0..n).map(idx32).collect();
630 let s = graph(n, &edges, &id);
631 let nat = fill(&s, &Natural.order(&s, OrderingTarget::Symmetric).unwrap());
632 assert_eq!(nat, n * (n + 1) / 2);
633 for o in [&Amd::default() as &dyn Ordering, &Colamd::default()] {
634 let p = o.order(&s, OrderingTarget::Symmetric).unwrap();
635 assert_eq!(fill(&s, &p), 2 * n - 1, "{} n = {n}", o.name());
636 assert!(
638 p.inv[0] as usize + 2 >= n,
639 "{}: hub at {}",
640 o.name(),
641 p.inv[0]
642 );
643 }
644 let m = incidence(n, &edges, &id);
645 let p = Colamd::default()
646 .order(&m, OrderingTarget::Columns)
647 .unwrap();
648 assert_eq!(fill(&s, &p), 2 * n - 1);
649 }
650 }
651
652 #[test]
655 fn ex_5_10_2_grids_beat_the_band() {
656 let mut rng = Rng::new(7);
657 for k in [6_usize, 10, 20, 30] {
658 let n = k * k;
659 let e = grid_edges(k);
660 let id: Vec<u32> = (0..n).map(idx32).collect();
661 let band = fill(&graph(n, &e, &id), &Perm::identity(n));
662 let label = random_label(&mut rng, n);
663 let s = graph(n, &e, &label);
664 let amd = fill(
665 &s,
666 &Amd::default().order(&s, OrderingTarget::Symmetric).unwrap(),
667 );
668 let sym = fill(
669 &s,
670 &Colamd::default()
671 .order(&s, OrderingTarget::Symmetric)
672 .unwrap(),
673 );
674 let m = incidence(n, &e, &label);
675 let col = fill(
676 &s,
677 &Colamd::default()
678 .order(&m, OrderingTarget::Columns)
679 .unwrap(),
680 );
681 assert!(
682 amd <= band && sym <= band && col <= band,
683 "k {k}: band {band}, amd {amd}, symamd {sym}, colamd {col}"
684 );
685 }
686 }
687
688 #[test]
693 fn brute_force_against_minimum_degree_and_optimum() {
694 let mut rng = Rng::new(2026);
695 let (mut amd_s, mut col_s, mut md_s, mut opt_s) = (0, 0, 0, 0);
696 for case in 0..300 {
697 let n = 2 + case % 6;
698 let s = random_graph(&mut rng, n, 20 + case % 50);
699 let opt = optimal_fill(&s);
700 let amd = fill(
701 &s,
702 &Amd::default().order(&s, OrderingTarget::Symmetric).unwrap(),
703 );
704 let mut edges = Vec::new();
705 for j in 0..n {
706 for &i in s.col(j) {
707 if (i as usize) < j {
708 edges.push((i as usize, j));
709 }
710 }
711 }
712 let id: Vec<u32> = (0..n).map(idx32).collect();
713 let m = incidence(n, &edges, &id);
714 let col = fill(
715 &s,
716 &Colamd::default()
717 .order(&m, OrderingTarget::Columns)
718 .unwrap(),
719 );
720 let md = fill(&s, &exact_minimum_degree(&s));
721 assert!(amd >= opt && col >= opt && md >= opt);
722 amd_s += amd;
723 col_s += col;
724 md_s += md;
725 opt_s += opt;
726 }
727 let r = |a: usize, b: usize| count_f64(a) / count_f64(b);
728 assert!(r(amd_s, md_s) <= 1.10, "AMD/MD {}", r(amd_s, md_s));
729 assert!(r(col_s, md_s) <= 1.10, "COLAMD/MD {}", r(col_s, md_s));
730 assert!(r(amd_s, opt_s) <= 1.25 && r(col_s, opt_s) <= 1.25);
731 }
732
733 #[test]
735 fn amd_tracks_exact_minimum_degree() {
736 let mut rng = Rng::new(11);
737 let (mut amd_s, mut md_s) = (0, 0);
738 for case in 0..40 {
739 let n = 20 + case;
740 let s = random_graph(&mut rng, n, 3 + case % 8);
741 amd_s += fill(
742 &s,
743 &Amd::default().order(&s, OrderingTarget::Symmetric).unwrap(),
744 );
745 md_s += fill(&s, &exact_minimum_degree(&s));
746 }
747 assert!(
748 count_f64(amd_s) <= 1.10 * count_f64(md_s),
749 "AMD {amd_s} MD {md_s}"
750 );
751 }
752
753 proptest! {
754 #[test]
757 fn orderings_are_permutations(m in 1_usize..40, n in 1_usize..40,
758 e in prop::collection::vec((0_usize..40, 0_usize..40), 0..300),
759 dense_line in 0_usize..3, knob in -1.0_f64..2.0) {
760 let mut e: Vec<(usize, usize)> = e.into_iter().map(|(i, j)| (i % m, j % n)).collect();
761 if dense_line == 1 {
762 e.extend((0..n).map(|j| (0, j)));
763 } else if dense_line == 2 {
764 e.extend((0..m).map(|i| (i, 0)));
765 }
766 let s = pat(m, n, &e);
767 let sq = pat(n, n, &e.iter().map(|&(i, j)| (i % n, j)).collect::<Vec<_>>());
768 let orderings: [&dyn Ordering; 5] = [
769 &Natural,
770 &Amd::default(),
771 &Amd { dense: Some(knob), aggressive: false },
772 &Colamd::default(),
773 &Colamd { dense_row: Some(knob), dense_col: Some(knob) },
774 ];
775 for o in orderings {
776 let p = o.order(&s, OrderingTarget::Columns).unwrap();
777 prop_assert_eq!(p.len(), n);
778 prop_assert!(p.check_invariants().is_ok());
779 let q = o.order(&sq, OrderingTarget::Symmetric).unwrap();
780 prop_assert_eq!(q.len(), n);
781 prop_assert!(q.check_invariants().is_ok());
782 prop_assert_eq!(o.order(&sq, OrderingTarget::Symmetric).unwrap(), q);
783 }
784 }
785
786 #[test]
790 fn trees_have_no_fill(n in 1_usize..120, seed in any::<u64>()) {
791 let mut rng = Rng::new(seed);
792 let e = tree_edges(&mut rng, n);
793 let label = random_label(&mut rng, n);
794 let s = graph(n, &e, &label);
795 let none = 2 * n - 1;
796 prop_assert!(fill(&s, &Perm::identity(n)) >= none);
797 prop_assert_eq!(fill(&s, &Amd::default().order(&s, OrderingTarget::Symmetric).unwrap()), none);
798 prop_assert_eq!(fill(&s, &Colamd::default().order(&s, OrderingTarget::Symmetric).unwrap()), none);
799 let m = incidence(n, &e, &label);
800 prop_assert_eq!(fill(&s, &Colamd::default().order(&m, OrderingTarget::Columns).unwrap()), none);
801 prop_assert_eq!(fill(&s, &Amd::default().order(&m, OrderingTarget::Columns).unwrap()), none);
802 }
803
804 #[test]
813 fn block_arrows_beat_the_natural_order(blocks in 2_usize..8, size in 2_usize..6,
814 w in 1_usize..4, seed in any::<u64>()) {
815 prop_assume!(size + w <= blocks * size);
819 let mut rng = Rng::new(seed);
820 let n = w + blocks * size;
821 let mut e = Vec::new();
822 for b in 0..blocks {
823 let base = w + b * size;
824 for x in 0..size {
825 for y in x + 1..size {
826 e.push((base + x, base + y));
827 }
828 for c in 0..w {
829 e.push((c, base + x));
830 }
831 }
832 }
833 let id: Vec<u32> = (0..n).map(idx32).collect();
834 let nat = fill(&graph(n, &e, &id), &Perm::identity(n));
835 let label = random_label(&mut rng, n);
836 let s = graph(n, &e, &label);
837 for o in [&Amd::default() as &dyn Ordering, &Colamd::default()] {
838 let f = fill(&s, &o.order(&s, OrderingTarget::Symmetric).unwrap());
839 prop_assert!(f <= nat, "{}: {} > natural {}", o.name(), f, nat);
840 }
841 }
842 }
843}