1use std::collections::VecDeque;
30
31use super::adjacency::AdjacencyGraph;
32use super::amd;
33use crate::error::SparseResult;
34
35#[derive(Debug, Clone)]
37pub struct NestedDissectionConfig {
38 pub min_subgraph_size: usize,
41 pub multi_level: bool,
44 pub max_coarsening_levels: usize,
47 pub kl_passes: usize,
50}
51
52impl Default for NestedDissectionConfig {
53 fn default() -> Self {
54 Self {
55 min_subgraph_size: 64,
56 multi_level: true,
57 max_coarsening_levels: 10,
58 kl_passes: 5,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct NestedDissectionResult {
66 pub perm: Vec<usize>,
68 pub inv_perm: Vec<usize>,
70 pub top_separator_size: usize,
72}
73
74pub fn nested_dissection(graph: &AdjacencyGraph) -> SparseResult<Vec<usize>> {
78 let config = NestedDissectionConfig::default();
79 nested_dissection_with_config(graph, &config)
80}
81
82pub fn nested_dissection_with_config(
84 graph: &AdjacencyGraph,
85 config: &NestedDissectionConfig,
86) -> SparseResult<Vec<usize>> {
87 let result = nested_dissection_full(graph, config)?;
88 Ok(result.perm)
89}
90
91pub fn nested_dissection_full(
93 graph: &AdjacencyGraph,
94 config: &NestedDissectionConfig,
95) -> SparseResult<NestedDissectionResult> {
96 let n = graph.num_nodes();
97 if n == 0 {
98 return Ok(NestedDissectionResult {
99 perm: Vec::new(),
100 inv_perm: Vec::new(),
101 top_separator_size: 0,
102 });
103 }
104
105 let nodes: Vec<usize> = (0..n).collect();
106 let mut perm = Vec::with_capacity(n);
107 let mut top_sep_size = 0;
108
109 nd_recurse(graph, &nodes, config, &mut perm, 0, &mut top_sep_size)?;
110
111 let mut inv_perm = vec![0usize; n];
113 for (new_i, &old_i) in perm.iter().enumerate() {
114 inv_perm[old_i] = new_i;
115 }
116
117 Ok(NestedDissectionResult {
118 perm,
119 inv_perm,
120 top_separator_size: top_sep_size,
121 })
122}
123
124fn nd_recurse(
126 graph: &AdjacencyGraph,
127 nodes: &[usize],
128 config: &NestedDissectionConfig,
129 perm: &mut Vec<usize>,
130 depth: usize,
131 top_sep_size: &mut usize,
132) -> SparseResult<()> {
133 let n = nodes.len();
134
135 if n == 0 {
136 return Ok(());
137 }
138
139 if n <= config.min_subgraph_size {
141 let (sub_graph, mapping) = graph.subgraph(nodes);
142 let amd_result = amd::amd(&sub_graph)?;
143 for &p in &amd_result.perm {
144 perm.push(mapping[p]);
145 }
146 return Ok(());
147 }
148
149 let (part_a, separator, part_b) = if config.multi_level {
151 find_separator_multilevel(graph, nodes, config)
152 } else {
153 find_separator_bfs(graph, nodes)
154 };
155
156 nd_recurse(graph, &part_a, config, perm, depth + 1, top_sep_size)?;
158
159 nd_recurse(graph, &part_b, config, perm, depth + 1, top_sep_size)?;
161
162 for &s in &separator {
164 perm.push(s);
165 }
166
167 if depth == 0 {
168 *top_sep_size = separator.len();
169 }
170
171 Ok(())
172}
173
174fn find_separator_bfs(
180 graph: &AdjacencyGraph,
181 nodes: &[usize],
182) -> (Vec<usize>, Vec<usize>, Vec<usize>) {
183 if nodes.is_empty() {
184 return (Vec::new(), Vec::new(), Vec::new());
185 }
186 if nodes.len() <= 2 {
187 return (nodes.to_vec(), Vec::new(), Vec::new());
189 }
190
191 let node_set: std::collections::HashSet<usize> = nodes.iter().copied().collect();
192
193 let start = nodes
195 .iter()
196 .copied()
197 .min_by_key(|&u| {
198 graph
199 .neighbors(u)
200 .iter()
201 .filter(|&&v| node_set.contains(&v))
202 .count()
203 })
204 .unwrap_or(nodes[0]);
205
206 let mut level = std::collections::HashMap::new();
208 let mut queue = VecDeque::new();
209 let mut order = Vec::new();
210 level.insert(start, 0usize);
211 queue.push_back(start);
212
213 while let Some(u) = queue.pop_front() {
214 order.push(u);
215 let l = level[&u];
216 for &v in graph.neighbors(u) {
217 if node_set.contains(&v) && !level.contains_key(&v) {
218 level.insert(v, l + 1);
219 queue.push_back(v);
220 }
221 }
222 }
223
224 for &u in nodes {
226 level.entry(u).or_insert_with(|| {
227 order.push(u);
228 0
229 });
230 }
231
232 let max_level = level.values().copied().max().unwrap_or(0);
233 if max_level == 0 {
234 let mid = nodes.len() / 2;
236 return (
237 nodes[..mid].to_vec(),
238 vec![nodes[mid]],
239 nodes[mid + 1..].to_vec(),
240 );
241 }
242
243 let sep_level = max_level / 2;
245
246 let mut part_a = Vec::new();
247 let mut separator = Vec::new();
248 let mut part_b = Vec::new();
249
250 for &u in nodes {
251 let l = level.get(&u).copied().unwrap_or(0);
252 if l < sep_level {
253 part_a.push(u);
254 } else if l == sep_level {
255 separator.push(u);
256 } else {
257 part_b.push(u);
258 }
259 }
260
261 if part_a.is_empty() || part_b.is_empty() {
264 let mid = order.len() / 2;
265 let sep_idx = mid.min(order.len().saturating_sub(1));
266 part_a = order[..sep_idx].to_vec();
267 separator = vec![order[sep_idx]];
268 part_b = if sep_idx + 1 < order.len() {
269 order[sep_idx + 1..].to_vec()
270 } else {
271 Vec::new()
272 };
273 }
274
275 (part_a, separator, part_b)
276}
277
278fn find_separator_multilevel(
285 graph: &AdjacencyGraph,
286 nodes: &[usize],
287 config: &NestedDissectionConfig,
288) -> (Vec<usize>, Vec<usize>, Vec<usize>) {
289 if nodes.len() <= 3 {
290 return find_separator_bfs(graph, nodes);
291 }
292
293 let (sub_graph, mapping) = graph.subgraph(nodes);
295 let sub_n = sub_graph.num_nodes();
296
297 let coarsening_levels = coarsen_graph(&sub_graph, config.max_coarsening_levels);
299
300 let coarsest = if coarsening_levels.is_empty() {
302 &sub_graph
303 } else {
304 coarsening_levels
305 .last()
306 .map(|l| &l.coarse_graph)
307 .unwrap_or(&sub_graph)
308 };
309
310 let mut partition = initial_bisection(coarsest);
311
312 for level in coarsening_levels.iter().rev() {
314 partition = uncoarsen_partition(&partition, &level.fine_to_coarse, level.fine_n);
315 kl_refine_partition(&level.coarse_graph, &mut partition, config.kl_passes);
316 }
317
318 if coarsening_levels.is_empty() {
320 kl_refine_partition(&sub_graph, &mut partition, config.kl_passes);
321 }
322
323 let mut part_a = Vec::new();
325 let mut separator = Vec::new();
326 let mut part_b = Vec::new();
327
328 let mut is_boundary = vec![false; sub_n];
330 for u in 0..sub_n {
331 for &v in sub_graph.neighbors(u) {
332 if partition[u] != partition[v] {
333 is_boundary[u] = true;
334 break;
335 }
336 }
337 }
338
339 let count_0: usize = partition.iter().filter(|&&p| p == 0).count();
341 let count_1 = sub_n - count_0;
342 let sep_side = if count_0 <= count_1 { 0 } else { 1 };
343
344 for u in 0..sub_n {
345 if is_boundary[u] && partition[u] == sep_side {
346 separator.push(mapping[u]);
347 } else if partition[u] == 0 && (sep_side != 0 || !is_boundary[u]) {
348 part_a.push(mapping[u]);
349 } else if partition[u] == 1 && (sep_side != 1 || !is_boundary[u]) {
350 part_b.push(mapping[u]);
351 }
352 }
353
354 if separator.is_empty() {
356 return find_separator_bfs(graph, nodes);
358 }
359
360 (part_a, separator, part_b)
361}
362
363struct CoarseningLevel {
365 coarse_graph: AdjacencyGraph,
367 fine_to_coarse: Vec<usize>,
369 fine_n: usize,
371}
372
373fn coarsen_graph(graph: &AdjacencyGraph, max_levels: usize) -> Vec<CoarseningLevel> {
375 let mut levels = Vec::new();
376 let mut current = graph.clone();
377
378 for _ in 0..max_levels {
379 let n = current.num_nodes();
380 if n <= 16 {
381 break; }
383
384 let mut matched = vec![false; n];
387 let mut coarse_id = vec![usize::MAX; n];
388 let mut next_id = 0usize;
389
390 let mut node_order: Vec<usize> = (0..n).collect();
392 node_order.sort_unstable_by_key(|&u| current.degree(u));
393
394 for &u in &node_order {
395 if matched[u] {
396 continue;
397 }
398
399 let best_neighbor = current
401 .neighbors(u)
402 .iter()
403 .copied()
404 .filter(|&v| !matched[v])
405 .max_by_key(|&v| current.degree(v));
406
407 matched[u] = true;
408 coarse_id[u] = next_id;
409
410 if let Some(v) = best_neighbor {
411 matched[v] = true;
412 coarse_id[v] = next_id;
413 }
414
415 next_id += 1;
416 }
417
418 for u in 0..n {
420 if coarse_id[u] == usize::MAX {
421 coarse_id[u] = next_id;
422 next_id += 1;
423 }
424 }
425
426 let coarse_n = next_id;
427 if coarse_n >= n * 3 / 4 {
428 break; }
430
431 let mut coarse_adj: Vec<Vec<usize>> = vec![Vec::new(); coarse_n];
433 for u in 0..n {
434 let cu = coarse_id[u];
435 for &v in current.neighbors(u) {
436 let cv = coarse_id[v];
437 if cu != cv {
438 coarse_adj[cu].push(cv);
439 }
440 }
441 }
442
443 let coarse_graph = AdjacencyGraph::from_adjacency_list(coarse_adj);
444
445 levels.push(CoarseningLevel {
446 coarse_graph: coarse_graph.clone(),
447 fine_to_coarse: coarse_id,
448 fine_n: n,
449 });
450
451 current = coarse_graph;
452 }
453
454 levels
455}
456
457fn initial_bisection(graph: &AdjacencyGraph) -> Vec<usize> {
459 let n = graph.num_nodes();
460 if n == 0 {
461 return Vec::new();
462 }
463
464 let start = (0..n).min_by_key(|&u| graph.degree(u)).unwrap_or(0);
466
467 let mut visited = vec![false; n];
468 let mut order = Vec::new();
469 let mut queue = VecDeque::new();
470 visited[start] = true;
471 queue.push_back(start);
472
473 while let Some(u) = queue.pop_front() {
474 order.push(u);
475 for &v in graph.neighbors(u) {
476 if !visited[v] {
477 visited[v] = true;
478 queue.push_back(v);
479 }
480 }
481 }
482
483 for u in 0..n {
485 if !visited[u] {
486 order.push(u);
487 }
488 }
489
490 let mid = order.len() / 2;
492 let mut partition = vec![0usize; n];
493 for &u in &order[mid..] {
494 partition[u] = 1;
495 }
496
497 partition
498}
499
500fn uncoarsen_partition(
502 coarse_partition: &[usize],
503 fine_to_coarse: &[usize],
504 fine_n: usize,
505) -> Vec<usize> {
506 let mut fine_partition = vec![0usize; fine_n];
507 for u in 0..fine_n {
508 let cu = fine_to_coarse[u];
509 if cu < coarse_partition.len() {
510 fine_partition[u] = coarse_partition[cu];
511 }
512 }
513 fine_partition
514}
515
516fn kl_refine_partition(graph: &AdjacencyGraph, partition: &mut [usize], max_passes: usize) {
518 let n = graph.num_nodes();
519 if n < 2 {
520 return;
521 }
522
523 for _pass in 0..max_passes {
524 let mut improved = false;
525
526 let mut best_gain = 0i64;
528 let mut best_node = None;
529
530 for u in 0..n {
531 let ext: i64 = graph
532 .neighbors(u)
533 .iter()
534 .filter(|&&v| partition[v] != partition[u])
535 .count() as i64;
536 let int: i64 = graph
537 .neighbors(u)
538 .iter()
539 .filter(|&&v| partition[v] == partition[u])
540 .count() as i64;
541 let gain = ext - int;
542
543 if gain > best_gain {
544 let count_same: usize = partition.iter().filter(|&&p| p == partition[u]).count();
546 if count_same > n / 4 {
547 best_gain = gain;
548 best_node = Some(u);
549 }
550 }
551 }
552
553 if let Some(u) = best_node {
554 partition[u] = 1 - partition[u];
555 improved = true;
556 }
557
558 if !improved {
559 break;
560 }
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 fn path_graph(n: usize) -> AdjacencyGraph {
569 let mut adj = vec![Vec::new(); n];
570 for i in 0..n.saturating_sub(1) {
571 adj[i].push(i + 1);
572 adj[i + 1].push(i);
573 }
574 AdjacencyGraph::from_adjacency_list(adj)
575 }
576
577 fn grid_graph(rows: usize, cols: usize) -> AdjacencyGraph {
578 let n = rows * cols;
579 let mut adj = vec![Vec::new(); n];
580 for r in 0..rows {
581 for c in 0..cols {
582 let u = r * cols + c;
583 if c + 1 < cols {
584 let v = r * cols + c + 1;
585 adj[u].push(v);
586 adj[v].push(u);
587 }
588 if r + 1 < rows {
589 let v = (r + 1) * cols + c;
590 adj[u].push(v);
591 adj[v].push(u);
592 }
593 }
594 }
595 AdjacencyGraph::from_adjacency_list(adj)
596 }
597
598 #[test]
599 fn test_nd_valid_permutation() {
600 let graph = path_graph(20);
601 let perm = nested_dissection(&graph).expect("ND");
602 assert_eq!(perm.len(), 20);
603 let mut sorted = perm.clone();
604 sorted.sort_unstable();
605 assert_eq!(sorted, (0..20).collect::<Vec<_>>());
606 }
607
608 #[test]
609 fn test_nd_grid_valid_permutation() {
610 let graph = grid_graph(4, 4);
611 let perm = nested_dissection(&graph).expect("ND grid");
612 assert_eq!(perm.len(), 16);
613 let mut sorted = perm.clone();
614 sorted.sort_unstable();
615 assert_eq!(sorted, (0..16).collect::<Vec<_>>());
616 }
617
618 #[test]
619 fn test_nd_small_graph_uses_amd() {
620 let graph = path_graph(10);
622 let config = NestedDissectionConfig {
623 min_subgraph_size: 64,
624 ..Default::default()
625 };
626 let perm = nested_dissection_with_config(&graph, &config).expect("ND small");
627 assert_eq!(perm.len(), 10);
628 let mut sorted = perm.clone();
629 sorted.sort_unstable();
630 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
631 }
632
633 #[test]
634 fn test_nd_large_graph() {
635 let graph = grid_graph(10, 10);
637 let config = NestedDissectionConfig {
638 min_subgraph_size: 16,
639 multi_level: true,
640 ..Default::default()
641 };
642 let result = nested_dissection_full(&graph, &config).expect("ND large");
643 assert_eq!(result.perm.len(), 100);
644 let mut sorted = result.perm.clone();
645 sorted.sort_unstable();
646 assert_eq!(sorted, (0..100).collect::<Vec<_>>());
647 }
648
649 #[test]
650 fn test_nd_empty_graph() {
651 let graph = AdjacencyGraph::from_adjacency_list(Vec::new());
652 let perm = nested_dissection(&graph).expect("ND empty");
653 assert!(perm.is_empty());
654 }
655
656 #[test]
657 fn test_nd_single_node() {
658 let graph = AdjacencyGraph::from_adjacency_list(vec![Vec::new()]);
659 let config = NestedDissectionConfig {
660 min_subgraph_size: 1,
661 multi_level: false,
662 ..Default::default()
663 };
664 let perm = nested_dissection_with_config(&graph, &config).expect("ND single");
665 assert_eq!(perm, vec![0]);
666 }
667
668 #[test]
669 fn test_nd_bfs_separator() {
670 let graph = path_graph(10);
672 let nodes: Vec<usize> = (0..10).collect();
673 let (a, sep, b) = find_separator_bfs(&graph, &nodes);
674 let total = a.len() + sep.len() + b.len();
676 assert_eq!(total, 10, "all nodes must be in some partition");
677 assert!(!sep.is_empty(), "separator should be non-empty");
679 }
680
681 #[test]
682 fn test_nd_disconnected() {
683 let mut adj = vec![Vec::new(); 8];
685 for i in 0..3 {
686 adj[i].push(i + 1);
687 adj[i + 1].push(i);
688 }
689 for i in 4..7 {
690 adj[i].push(i + 1);
691 adj[i + 1].push(i);
692 }
693 let graph = AdjacencyGraph::from_adjacency_list(adj);
694 let config = NestedDissectionConfig {
695 min_subgraph_size: 2,
696 multi_level: false,
697 ..Default::default()
698 };
699 let perm = nested_dissection_with_config(&graph, &config).expect("ND disconnected");
700 assert_eq!(perm.len(), 8);
701 let mut sorted = perm.clone();
702 sorted.sort_unstable();
703 assert_eq!(sorted, (0..8).collect::<Vec<_>>());
704 }
705}