|
| 1 | +//! Detect problems that have no reduction path connecting them to the main graph. |
| 2 | +//! |
| 3 | +//! Finds: |
| 4 | +//! 1. Completely isolated problem types (no reductions in or out) |
| 5 | +//! 2. Disconnected components (groups not reachable from the largest component) |
| 6 | +//! |
| 7 | +//! Run with: `cargo run --example detect_isolated_problems` |
| 8 | +
|
| 9 | +use problemreductions::rules::ReductionGraph; |
| 10 | +use std::collections::{BTreeMap, BTreeSet, VecDeque}; |
| 11 | + |
| 12 | +fn main() { |
| 13 | + let graph = ReductionGraph::new(); |
| 14 | + |
| 15 | + let mut types = graph.problem_types(); |
| 16 | + types.sort(); |
| 17 | + |
| 18 | + // Build undirected adjacency at the problem-type level |
| 19 | + let mut adj: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new(); |
| 20 | + for &name in &types { |
| 21 | + adj.entry(name).or_default(); |
| 22 | + for edge in graph.outgoing_reductions(name) { |
| 23 | + adj.entry(name).or_default().insert(edge.target_name); |
| 24 | + adj.entry(edge.target_name).or_default().insert(name); |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + // Find connected components via BFS |
| 29 | + let mut visited: BTreeSet<&str> = BTreeSet::new(); |
| 30 | + let mut components: Vec<Vec<&str>> = Vec::new(); |
| 31 | + |
| 32 | + for &name in &types { |
| 33 | + if visited.contains(name) { |
| 34 | + continue; |
| 35 | + } |
| 36 | + let mut component = Vec::new(); |
| 37 | + let mut queue = VecDeque::new(); |
| 38 | + queue.push_back(name); |
| 39 | + visited.insert(name); |
| 40 | + |
| 41 | + while let Some(current) = queue.pop_front() { |
| 42 | + component.push(current); |
| 43 | + if let Some(neighbors) = adj.get(current) { |
| 44 | + for &neighbor in neighbors { |
| 45 | + if visited.insert(neighbor) { |
| 46 | + queue.push_back(neighbor); |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | + } |
| 51 | + component.sort(); |
| 52 | + components.push(component); |
| 53 | + } |
| 54 | + |
| 55 | + // Sort components by size (largest first) |
| 56 | + components.sort_by(|a, b| b.len().cmp(&a.len())); |
| 57 | + |
| 58 | + // Identify isolated types (no edges at all) |
| 59 | + let isolated: Vec<&str> = types |
| 60 | + .iter() |
| 61 | + .copied() |
| 62 | + .filter(|name| adj.get(name).is_some_and(|n| n.is_empty())) |
| 63 | + .collect(); |
| 64 | + |
| 65 | + // Report |
| 66 | + println!("Reduction Graph Connectivity Report"); |
| 67 | + println!("===================================="); |
| 68 | + println!("Total problem types: {}", types.len()); |
| 69 | + println!("Total reductions: {}", graph.num_reductions()); |
| 70 | + println!("Connected components: {}", components.len()); |
| 71 | + println!(); |
| 72 | + |
| 73 | + if !isolated.is_empty() { |
| 74 | + println!( |
| 75 | + "Isolated problems ({}) — no reductions in or out:", |
| 76 | + isolated.len() |
| 77 | + ); |
| 78 | + for name in &isolated { |
| 79 | + let num_variants = graph.variants_for(name).len(); |
| 80 | + println!(" {name} ({num_variants} variant(s))"); |
| 81 | + } |
| 82 | + println!(); |
| 83 | + } |
| 84 | + |
| 85 | + if components.len() > 1 { |
| 86 | + println!("Disconnected components:"); |
| 87 | + for (i, comp) in components.iter().enumerate() { |
| 88 | + let marker = if i == 0 { " (main)" } else { "" }; |
| 89 | + println!("\n Component {}{marker} — {} types:", i + 1, comp.len()); |
| 90 | + for name in comp { |
| 91 | + let num_variants = graph.variants_for(name).len(); |
| 92 | + let out_count = graph.outgoing_reductions(name).len(); |
| 93 | + let in_count = graph.incoming_reductions(name).len(); |
| 94 | + println!(" {name} ({num_variants} variant(s), {out_count} out, {in_count} in)"); |
| 95 | + } |
| 96 | + } |
| 97 | + } else { |
| 98 | + println!("All problem types with reductions are in a single connected component."); |
| 99 | + } |
| 100 | + |
| 101 | + // Also report at the variant level |
| 102 | + println!(); |
| 103 | + println!("Variant-level detail for isolated problems:"); |
| 104 | + for name in &isolated { |
| 105 | + let variants = graph.variants_for(name); |
| 106 | + for v in &variants { |
| 107 | + let label = if v.is_empty() { |
| 108 | + name.to_string() |
| 109 | + } else { |
| 110 | + let parts: Vec<String> = |
| 111 | + v.iter().map(|(k, val)| format!("{k}: {val}")).collect(); |
| 112 | + format!("{name} {{{}}}", parts.join(", ")) |
| 113 | + }; |
| 114 | + if let Some(c) = graph.variant_complexity(name, v) { |
| 115 | + println!(" {label} complexity: {c}"); |
| 116 | + } else { |
| 117 | + println!(" {label}"); |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + // Exit with non-zero if there are isolated types or multiple components |
| 123 | + if !isolated.is_empty() || components.len() > 1 { |
| 124 | + std::process::exit(1); |
| 125 | + } |
| 126 | +} |
0 commit comments