1use std::collections::VecDeque;
18
19use crate::error::{SolverError, SolverResult};
20
21#[derive(Debug, Clone)]
27pub struct Permutation {
28 pub perm: Vec<usize>,
30 pub iperm: Vec<usize>,
32}
33
34impl Permutation {
35 pub fn new(perm: Vec<usize>) -> SolverResult<Self> {
38 let n = perm.len();
39 let mut iperm = vec![0usize; n];
40 let mut seen = vec![false; n];
41 for (i, &p) in perm.iter().enumerate() {
42 if p >= n {
43 return Err(SolverError::InternalError(format!(
44 "permutation index {p} out of range for size {n}"
45 )));
46 }
47 if seen[p] {
48 return Err(SolverError::InternalError(format!(
49 "duplicate permutation index {p}"
50 )));
51 }
52 seen[p] = true;
53 iperm[p] = i;
54 }
55 Ok(Self { perm, iperm })
56 }
57
58 pub fn identity(n: usize) -> Self {
60 Self {
61 perm: (0..n).collect(),
62 iperm: (0..n).collect(),
63 }
64 }
65
66 pub fn apply<T: Copy>(&self, data: &[T]) -> Vec<T> {
68 self.perm.iter().map(|&p| data[p]).collect()
69 }
70
71 pub fn compose(&self, other: &Permutation) -> SolverResult<Permutation> {
73 let composed: Vec<usize> = other.perm.iter().map(|&i| self.perm[i]).collect();
74 Permutation::new(composed)
75 }
76
77 pub fn inverse(&self) -> &[usize] {
79 &self.iperm
80 }
81}
82
83#[derive(Debug, Clone)]
92pub struct AdjacencyGraph {
93 pub num_vertices: usize,
95 pub adj_ptr: Vec<usize>,
97 pub adj_list: Vec<usize>,
99}
100
101impl AdjacencyGraph {
102 pub fn from_symmetric_csr(row_ptr: &[i32], col_indices: &[i32], n: usize) -> Self {
108 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
109 for row in 0..n {
110 let start = row_ptr[row] as usize;
111 let end = row_ptr[row + 1] as usize;
112 for &col_i32 in &col_indices[start..end] {
113 let col = col_i32 as usize;
114 if col != row && col < n {
115 adj[row].push(col);
116 }
117 }
118 }
119 for list in &mut adj {
121 list.sort_unstable();
122 list.dedup();
123 }
124 let mut adj_ptr = Vec::with_capacity(n + 1);
126 let mut adj_list = Vec::new();
127 adj_ptr.push(0);
128 for list in &adj {
129 adj_list.extend_from_slice(list);
130 adj_ptr.push(adj_list.len());
131 }
132 Self {
133 num_vertices: n,
134 adj_ptr,
135 adj_list,
136 }
137 }
138
139 pub fn degree(&self, v: usize) -> usize {
141 self.adj_ptr[v + 1] - self.adj_ptr[v]
142 }
143
144 pub fn neighbors(&self, v: usize) -> &[usize] {
146 &self.adj_list[self.adj_ptr[v]..self.adj_ptr[v + 1]]
147 }
148}
149
150#[derive(Debug, Clone)]
156pub struct SeparatorResult {
157 pub separator: Vec<usize>,
159 pub part_a: Vec<usize>,
161 pub part_b: Vec<usize>,
163 pub balance_ratio: f64,
165}
166
167#[derive(Debug, Clone)]
173pub struct OrderingQuality {
174 pub estimated_fill: usize,
176 pub separator_size: usize,
178 pub balance_ratio: f64,
180}
181
182fn bfs_levels(graph: &AdjacencyGraph, source: usize, in_subgraph: &[bool]) -> Vec<Vec<usize>> {
188 let n = graph.num_vertices;
189 let mut visited = vec![false; n];
190 visited[source] = true;
191 let mut levels: Vec<Vec<usize>> = vec![vec![source]];
192 let mut queue = VecDeque::new();
193 queue.push_back(source);
194 while !queue.is_empty() {
195 let mut next_level = Vec::new();
196 let count = queue.len();
197 for _ in 0..count {
198 if let Some(v) = queue.pop_front() {
199 for &nb in graph.neighbors(v) {
200 if in_subgraph[nb] && !visited[nb] {
201 visited[nb] = true;
202 next_level.push(nb);
203 queue.push_back(nb);
204 }
205 }
206 }
207 }
208 if !next_level.is_empty() {
209 levels.push(next_level);
210 }
211 }
212 levels
213}
214
215fn pseudo_peripheral(graph: &AdjacencyGraph, subgraph: &[usize]) -> usize {
217 if subgraph.is_empty() {
218 return 0;
219 }
220 let n = graph.num_vertices;
221 let mut in_sub = vec![false; n];
222 for &v in subgraph {
223 in_sub[v] = true;
224 }
225 let mut start = subgraph[0];
227 let mut min_deg = usize::MAX;
228 for &v in subgraph {
229 let d = graph.neighbors(v).iter().filter(|&&nb| in_sub[nb]).count();
230 if d < min_deg {
231 min_deg = d;
232 start = v;
233 }
234 }
235 for _ in 0..2 {
237 let levels = bfs_levels(graph, start, &in_sub);
238 if let Some(last) = levels.last() {
239 let mut best = last[0];
241 let mut best_deg = usize::MAX;
242 for &v in last {
243 let d = graph.neighbors(v).iter().filter(|&&nb| in_sub[nb]).count();
244 if d < best_deg {
245 best_deg = d;
246 best = v;
247 }
248 }
249 start = best;
250 }
251 }
252 start
253}
254
255fn coarsen(graph: &AdjacencyGraph, subgraph: &[usize]) -> (Vec<i32>, usize) {
262 let n = graph.num_vertices;
263 let mut coarse_map = vec![-1i32; n];
264 let mut matched = vec![false; n];
265 let mut coarse_id = 0usize;
266
267 let mut sorted = subgraph.to_vec();
269 sorted.sort_unstable_by_key(|&v| graph.degree(v));
270
271 let in_sub: Vec<bool> = {
272 let mut s = vec![false; n];
273 for &v in subgraph {
274 s[v] = true;
275 }
276 s
277 };
278
279 for &v in &sorted {
280 if matched[v] {
281 continue;
282 }
283 let mut best_nb = None;
285 let mut best_weight = 0usize;
286 for &nb in graph.neighbors(v) {
287 if in_sub[nb] && !matched[nb] && nb != v {
288 let w = graph.degree(v) + graph.degree(nb);
289 if w > best_weight {
290 best_weight = w;
291 best_nb = Some(nb);
292 }
293 }
294 }
295 let cid = coarse_id as i32;
296 coarse_map[v] = cid;
297 matched[v] = true;
298 if let Some(nb) = best_nb {
299 coarse_map[nb] = cid;
300 matched[nb] = true;
301 }
302 coarse_id += 1;
303 }
304 (coarse_map, coarse_id)
305}
306
307fn bfs_bisect(graph: &AdjacencyGraph, subgraph: &[usize]) -> SeparatorResult {
313 if subgraph.len() <= 1 {
314 return SeparatorResult {
315 separator: subgraph.to_vec(),
316 part_a: Vec::new(),
317 part_b: Vec::new(),
318 balance_ratio: 1.0,
319 };
320 }
321 let n = graph.num_vertices;
322 let mut in_sub = vec![false; n];
323 for &v in subgraph {
324 in_sub[v] = true;
325 }
326 let start = pseudo_peripheral(graph, subgraph);
327 let levels = bfs_levels(graph, start, &in_sub);
328
329 if levels.len() <= 2 {
330 let half = subgraph.len() / 2;
332 let part_a = subgraph[..half].to_vec();
333 let separator = vec![subgraph[half]];
334 let part_b = if half + 1 < subgraph.len() {
335 subgraph[half + 1..].to_vec()
336 } else {
337 Vec::new()
338 };
339 let balance = compute_balance(part_a.len(), part_b.len());
340 return SeparatorResult {
341 separator,
342 part_a,
343 part_b,
344 balance_ratio: balance,
345 };
346 }
347
348 let mid = levels.len() / 2;
350 let separator = levels[mid].clone();
351 let mut part_a = Vec::new();
352 let mut part_b = Vec::new();
353 for (i, level) in levels.iter().enumerate() {
354 if i < mid {
355 part_a.extend_from_slice(level);
356 } else if i > mid {
357 part_b.extend_from_slice(level);
358 }
359 }
360 let balance = compute_balance(part_a.len(), part_b.len());
361 SeparatorResult {
362 separator,
363 part_a,
364 part_b,
365 balance_ratio: balance,
366 }
367}
368
369fn compute_balance(a: usize, b: usize) -> f64 {
371 if a == 0 && b == 0 {
372 return 1.0;
373 }
374 let min_v = a.min(b) as f64;
375 let max_v = a.max(b) as f64;
376 if max_v == 0.0 { 1.0 } else { min_v / max_v }
377}
378
379fn fm_refine(graph: &AdjacencyGraph, result: &mut SeparatorResult) {
385 let n = graph.num_vertices;
386 let mut partition = vec![0u8; n];
388 for &v in &result.part_a {
389 partition[v] = 0;
390 }
391 for &v in &result.part_b {
392 partition[v] = 1;
393 }
394 for &v in &result.separator {
395 partition[v] = 2;
396 }
397
398 let total = result.part_a.len() + result.part_b.len() + result.separator.len();
399 if total <= 3 {
400 return;
401 }
402
403 let mut improved = true;
405 let mut iterations = 0;
406 while improved && iterations < 10 {
407 improved = false;
408 iterations += 1;
409 let sep_copy = result.separator.clone();
410 for &sv in &sep_copy {
411 let mut na = 0usize;
413 let mut nb = 0usize;
414 for &nb_v in graph.neighbors(sv) {
415 match partition[nb_v] {
416 0 => na += 1,
417 1 => nb += 1,
418 _ => {}
419 }
420 }
421 if na > 0 && nb == 0 {
423 partition[sv] = 0;
424 result.part_a.push(sv);
425 result.separator.retain(|&x| x != sv);
426 improved = true;
427 } else if nb > 0 && na == 0 {
428 partition[sv] = 1;
429 result.part_b.push(sv);
430 result.separator.retain(|&x| x != sv);
431 improved = true;
432 }
433 }
434 }
435
436 let a_len = result.part_a.len();
438 let b_len = result.part_b.len();
439 if a_len > 2 * b_len + 2 {
440 let mut candidates: Vec<usize> = result
442 .part_a
443 .iter()
444 .copied()
445 .filter(|&v| {
446 graph
447 .neighbors(v)
448 .iter()
449 .any(|&nb| partition[nb] == 1 || partition[nb] == 2)
450 })
451 .collect();
452 candidates.sort_unstable_by_key(|&v| graph.degree(v));
453 let to_move = (a_len - b_len) / 4;
454 for &v in candidates.iter().take(to_move) {
455 partition[v] = 2;
456 result.separator.push(v);
457 result.part_a.retain(|&x| x != v);
458 }
459 } else if b_len > 2 * a_len + 2 {
460 let mut candidates: Vec<usize> = result
461 .part_b
462 .iter()
463 .copied()
464 .filter(|&v| {
465 graph
466 .neighbors(v)
467 .iter()
468 .any(|&nb| partition[nb] == 0 || partition[nb] == 2)
469 })
470 .collect();
471 candidates.sort_unstable_by_key(|&v| graph.degree(v));
472 let to_move = (b_len - a_len) / 4;
473 for &v in candidates.iter().take(to_move) {
474 partition[v] = 2;
475 result.separator.push(v);
476 result.part_b.retain(|&x| x != v);
477 }
478 }
479
480 result.balance_ratio = compute_balance(result.part_a.len(), result.part_b.len());
481}
482
483pub struct VertexSeparator;
489
490impl VertexSeparator {
491 pub fn find_separator(graph: &AdjacencyGraph, subgraph: &[usize]) -> SeparatorResult {
495 if subgraph.len() <= 3 {
496 return Self::trivial_separator(subgraph);
497 }
498 let (coarse_map, coarse_count) = coarsen(graph, subgraph);
500 if coarse_count >= subgraph.len() || coarse_count <= 1 {
501 let mut result = bfs_bisect(graph, subgraph);
503 fm_refine(graph, &mut result);
504 return result;
505 }
506
507 let mut coarse_repr: Vec<Option<usize>> = vec![None; coarse_count];
509 for &v in subgraph {
510 let cid = coarse_map[v];
511 if cid >= 0 {
512 let cid = cid as usize;
513 if coarse_repr[cid].is_none() {
514 coarse_repr[cid] = Some(v);
515 }
516 }
517 }
518 let coarse_verts: Vec<usize> = coarse_repr.iter().filter_map(|&x| x).collect();
519
520 let coarse_result = bfs_bisect(graph, &coarse_verts);
522
523 let n = graph.num_vertices;
525 let mut fine_part = vec![0u8; n]; for &v in &coarse_result.part_a {
527 let cid = coarse_map[v];
528 for &fv in subgraph {
529 if coarse_map[fv] == cid {
530 fine_part[fv] = 0;
531 }
532 }
533 }
534 for &v in &coarse_result.part_b {
535 let cid = coarse_map[v];
536 for &fv in subgraph {
537 if coarse_map[fv] == cid {
538 fine_part[fv] = 1;
539 }
540 }
541 }
542 for &v in &coarse_result.separator {
543 let cid = coarse_map[v];
544 for &fv in subgraph {
545 if coarse_map[fv] == cid {
546 fine_part[fv] = 2;
547 }
548 }
549 }
550
551 let mut separator = Vec::new();
553 let mut part_a = Vec::new();
554 let mut part_b = Vec::new();
555 for &v in subgraph {
556 if fine_part[v] == 2 {
557 separator.push(v);
558 } else {
559 let is_boundary = graph
560 .neighbors(v)
561 .iter()
562 .any(|&nb| fine_part[nb] != fine_part[v] && fine_part[nb] != 2);
563 if is_boundary {
564 separator.push(v);
565 } else if fine_part[v] == 0 {
566 part_a.push(v);
567 } else {
568 part_b.push(v);
569 }
570 }
571 }
572
573 let balance = compute_balance(part_a.len(), part_b.len());
574 let mut result = SeparatorResult {
575 separator,
576 part_a,
577 part_b,
578 balance_ratio: balance,
579 };
580 fm_refine(graph, &mut result);
581 result
582 }
583
584 fn trivial_separator(subgraph: &[usize]) -> SeparatorResult {
586 match subgraph.len() {
587 0 => SeparatorResult {
588 separator: Vec::new(),
589 part_a: Vec::new(),
590 part_b: Vec::new(),
591 balance_ratio: 1.0,
592 },
593 1 => SeparatorResult {
594 separator: subgraph.to_vec(),
595 part_a: Vec::new(),
596 part_b: Vec::new(),
597 balance_ratio: 1.0,
598 },
599 2 => SeparatorResult {
600 separator: vec![subgraph[0]],
601 part_a: vec![subgraph[1]],
602 part_b: Vec::new(),
603 balance_ratio: 0.0,
604 },
605 _ => SeparatorResult {
606 separator: vec![subgraph[1]],
607 part_a: vec![subgraph[0]],
608 part_b: vec![subgraph[2]],
609 balance_ratio: 1.0,
610 },
611 }
612 }
613}
614
615pub struct MinimumDegreeOrdering;
621
622impl MinimumDegreeOrdering {
623 pub fn compute(graph: &AdjacencyGraph, vertices: &[usize]) -> Vec<usize> {
627 if vertices.is_empty() {
628 return Vec::new();
629 }
630 let n = graph.num_vertices;
631 let mut in_sub = vec![false; n];
632 for &v in vertices {
633 in_sub[v] = true;
634 }
635 let mut eliminated = vec![false; n];
636 let mut order = Vec::with_capacity(vertices.len());
637
638 for _ in 0..vertices.len() {
639 let mut best = None;
641 let mut best_deg = usize::MAX;
642 for &v in vertices {
643 if eliminated[v] {
644 continue;
645 }
646 let deg = graph
647 .neighbors(v)
648 .iter()
649 .filter(|&&nb| in_sub[nb] && !eliminated[nb])
650 .count();
651 if deg < best_deg {
652 best_deg = deg;
653 best = Some(v);
654 }
655 }
656 if let Some(v) = best {
657 order.push(v);
658 eliminated[v] = true;
659 }
660 }
661 order
662 }
663}
664
665const ND_THRESHOLD: usize = 32;
671
672const MAX_DEPTH: usize = 64;
674
675pub struct NestedDissectionOrdering;
681
682impl NestedDissectionOrdering {
683 pub fn compute(graph: &AdjacencyGraph) -> SolverResult<Permutation> {
685 let n = graph.num_vertices;
686 if n == 0 {
687 return Permutation::new(Vec::new());
688 }
689 let vertices: Vec<usize> = (0..n).collect();
690 let mut order = Vec::with_capacity(n);
691 Self::recurse(graph, &vertices, &mut order, 0);
692 Permutation::new(order)
695 }
696
697 fn recurse(graph: &AdjacencyGraph, subgraph: &[usize], order: &mut Vec<usize>, depth: usize) {
699 if subgraph.len() <= ND_THRESHOLD || depth >= MAX_DEPTH {
700 let md_order = MinimumDegreeOrdering::compute(graph, subgraph);
701 order.extend(md_order);
702 return;
703 }
704
705 let sep = VertexSeparator::find_separator(graph, subgraph);
706
707 if !sep.part_a.is_empty() {
709 Self::recurse(graph, &sep.part_a, order, depth + 1);
710 }
711 if !sep.part_b.is_empty() {
712 Self::recurse(graph, &sep.part_b, order, depth + 1);
713 }
714 order.extend(&sep.separator);
716 }
717}
718
719pub fn analyze_ordering(graph: &AdjacencyGraph, perm: &Permutation) -> OrderingQuality {
726 let n = graph.num_vertices;
727 if n == 0 {
728 return OrderingQuality {
729 estimated_fill: 0,
730 separator_size: 0,
731 balance_ratio: 1.0,
732 };
733 }
734
735 let iperm = perm.inverse();
737 let mut fill = 0usize;
738 let mut parent = vec![n; n]; for i in 0..n {
741 let orig = perm.perm[i]; let mut visited = vec![false; n];
743 visited[i] = true;
744 for &nb in graph.neighbors(orig) {
745 let mut j = iperm[nb]; while j < n && !visited[j] && j > i {
748 visited[j] = true;
749 fill += 1;
750 if parent[j] == n || parent[j] > i {
751 if parent[j] == n {
754 parent[j] = i;
755 }
756 }
757 j = parent[j];
758 }
759 }
760 }
761
762 let vertices: Vec<usize> = (0..n).collect();
764 let sep = if n > 3 {
765 VertexSeparator::find_separator(graph, &vertices)
766 } else {
767 SeparatorResult {
768 separator: Vec::new(),
769 part_a: Vec::new(),
770 part_b: Vec::new(),
771 balance_ratio: 1.0,
772 }
773 };
774
775 OrderingQuality {
776 estimated_fill: fill,
777 separator_size: sep.separator.len(),
778 balance_ratio: sep.balance_ratio,
779 }
780}
781
782#[cfg(test)]
787mod tests {
788 use super::*;
789
790 fn path_graph(n: usize) -> AdjacencyGraph {
792 let mut row_ptr = vec![0i32];
793 let mut col_indices = Vec::new();
794 for i in 0..n {
795 if i > 0 {
796 col_indices.push((i - 1) as i32);
797 }
798 if i + 1 < n {
799 col_indices.push((i + 1) as i32);
800 }
801 row_ptr.push(col_indices.len() as i32);
802 }
803 AdjacencyGraph::from_symmetric_csr(&row_ptr, &col_indices, n)
804 }
805
806 fn grid_graph(rows: usize, cols: usize) -> AdjacencyGraph {
808 let n = rows * cols;
809 let mut row_ptr = vec![0i32];
810 let mut col_indices = Vec::new();
811 for r in 0..rows {
812 for c in 0..cols {
813 let v = r * cols + c;
814 if r > 0 {
815 col_indices.push((v - cols) as i32);
816 }
817 if c > 0 {
818 col_indices.push((v - 1) as i32);
819 }
820 if c + 1 < cols {
821 col_indices.push((v + 1) as i32);
822 }
823 if r + 1 < rows {
824 col_indices.push((v + cols) as i32);
825 }
826 row_ptr.push(col_indices.len() as i32);
827 }
828 }
829 AdjacencyGraph::from_symmetric_csr(&row_ptr, &col_indices, n)
830 }
831
832 #[test]
833 fn permutation_identity() {
834 let p = Permutation::identity(5);
835 assert_eq!(p.perm, vec![0, 1, 2, 3, 4]);
836 assert_eq!(p.inverse(), &[0, 1, 2, 3, 4]);
837 }
838
839 #[test]
840 fn permutation_valid() {
841 let p = Permutation::new(vec![2, 0, 1]).expect("valid perm");
842 assert_eq!(p.perm, vec![2, 0, 1]);
843 assert_eq!(p.iperm, vec![1, 2, 0]);
844 }
845
846 #[test]
847 fn permutation_invalid_duplicate() {
848 let result = Permutation::new(vec![0, 0, 1]);
849 assert!(result.is_err());
850 }
851
852 #[test]
853 fn permutation_invalid_out_of_range() {
854 let result = Permutation::new(vec![0, 5, 2]);
855 assert!(result.is_err());
856 }
857
858 #[test]
859 fn permutation_apply() {
860 let p = Permutation::new(vec![2, 0, 1]).expect("valid perm");
861 let data = vec![10, 20, 30];
862 let result = p.apply(&data);
863 assert_eq!(result, vec![30, 10, 20]);
864 }
865
866 #[test]
867 fn permutation_compose() {
868 let p1 = Permutation::new(vec![1, 2, 0]).expect("valid");
869 let p2 = Permutation::new(vec![2, 0, 1]).expect("valid");
870 let composed = p1.compose(&p2).expect("compose");
871 assert_eq!(composed.perm, vec![0, 1, 2]);
875 }
876
877 #[test]
878 fn adjacency_graph_path() {
879 let g = path_graph(5);
880 assert_eq!(g.num_vertices, 5);
881 assert_eq!(g.degree(0), 1);
882 assert_eq!(g.degree(2), 2);
883 assert_eq!(g.degree(4), 1);
884 assert_eq!(g.neighbors(2), &[1, 3]);
885 }
886
887 #[test]
888 fn adjacency_graph_grid() {
889 let g = grid_graph(3, 3);
890 assert_eq!(g.num_vertices, 9);
891 assert_eq!(g.degree(0), 2);
893 assert_eq!(g.degree(4), 4);
895 }
896
897 #[test]
898 fn separator_splits_graph() {
899 let g = grid_graph(4, 4);
900 let verts: Vec<usize> = (0..16).collect();
901 let sep = VertexSeparator::find_separator(&g, &verts);
902 let total = sep.separator.len() + sep.part_a.len() + sep.part_b.len();
904 assert_eq!(total, 16);
905 assert!(!sep.separator.is_empty());
907 let n = g.num_vertices;
909 let mut part = vec![0u8; n];
910 for &v in &sep.part_a {
911 part[v] = 1;
912 }
913 for &v in &sep.part_b {
914 part[v] = 2;
915 }
916 for &v in &sep.part_a {
917 for &nb in g.neighbors(v) {
918 assert_ne!(part[nb], 2, "edge {v}-{nb} crosses separator");
919 }
920 }
921 }
922
923 #[test]
924 fn nd_ordering_valid_permutation() {
925 let g = grid_graph(4, 4);
926 let perm = NestedDissectionOrdering::compute(&g).expect("nd ordering");
927 assert_eq!(perm.perm.len(), 16);
928 let mut sorted = perm.perm.clone();
930 sorted.sort_unstable();
931 assert_eq!(sorted, (0..16).collect::<Vec<_>>());
932 }
933
934 #[test]
935 fn nd_ordering_empty_graph() {
936 let g = AdjacencyGraph::from_symmetric_csr(&[0], &[], 0);
937 let perm = NestedDissectionOrdering::compute(&g).expect("empty");
938 assert!(perm.perm.is_empty());
939 }
940
941 #[test]
942 fn nd_ordering_single_vertex() {
943 let g = AdjacencyGraph::from_symmetric_csr(&[0, 0], &[], 1);
944 let perm = NestedDissectionOrdering::compute(&g).expect("single");
945 assert_eq!(perm.perm, vec![0]);
946 }
947
948 #[test]
949 fn nd_reduces_fill_vs_natural() {
950 let g = grid_graph(8, 8);
951 let nd_perm = NestedDissectionOrdering::compute(&g).expect("nd");
952 let natural_perm = Permutation::identity(64);
953 let nd_quality = analyze_ordering(&g, &nd_perm);
954 let nat_quality = analyze_ordering(&g, &natural_perm);
955 assert!(
957 nd_quality.estimated_fill <= nat_quality.estimated_fill,
958 "ND fill {} should be <= natural fill {}",
959 nd_quality.estimated_fill,
960 nat_quality.estimated_fill
961 );
962 }
963
964 #[test]
965 fn minimum_degree_ordering_valid() {
966 let g = path_graph(10);
967 let verts: Vec<usize> = (0..10).collect();
968 let order = MinimumDegreeOrdering::compute(&g, &verts);
969 assert_eq!(order.len(), 10);
970 let mut sorted = order.clone();
971 sorted.sort_unstable();
972 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
973 }
974
975 #[test]
976 fn ordering_quality_metrics() {
977 let g = grid_graph(4, 4);
978 let perm = NestedDissectionOrdering::compute(&g).expect("nd");
979 let quality = analyze_ordering(&g, &perm);
980 assert!(quality.estimated_fill < 200, "fill should be reasonable");
981 assert!(quality.balance_ratio >= 0.0 && quality.balance_ratio <= 1.0);
982 }
983
984 #[test]
985 fn larger_grid_nd() {
986 let g = grid_graph(16, 16);
987 let perm = NestedDissectionOrdering::compute(&g).expect("nd 16x16");
988 assert_eq!(perm.perm.len(), 256);
989 let mut sorted = perm.perm.clone();
990 sorted.sort_unstable();
991 assert_eq!(sorted, (0..256).collect::<Vec<_>>());
992 }
993}