problemreductions/rules/
analysis.rs1use crate::rules::graph::ReductionGraph;
4use std::collections::{BTreeMap, BTreeSet};
5
6#[derive(Debug, Clone)]
10pub struct ConnectivityReport {
11 pub total_types: usize,
13 pub total_reductions: usize,
15 pub isolated: Vec<IsolatedProblem>,
17 pub components: Vec<Vec<&'static str>>,
20}
21
22#[derive(Debug, Clone)]
24pub struct IsolatedProblem {
25 pub name: &'static str,
26 pub num_variants: usize,
27 pub variant_complexities: Vec<(BTreeMap<String, String>, Option<String>)>,
29}
30
31pub fn check_connectivity(graph: &ReductionGraph) -> ConnectivityReport {
33 let mut types = graph.problem_types();
34 types.sort();
35
36 let mut adj: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
38 for &name in &types {
39 adj.entry(name).or_default();
40 for edge in graph.outgoing_reductions(name) {
41 adj.entry(name).or_default().insert(edge.target_name);
42 adj.entry(edge.target_name).or_default().insert(name);
43 }
44 }
45
46 let mut visited: BTreeSet<&str> = BTreeSet::new();
48 let mut components: Vec<Vec<&str>> = Vec::new();
49
50 for &name in &types {
51 if visited.contains(name) {
52 continue;
53 }
54 let mut component = Vec::new();
55 let mut queue = std::collections::VecDeque::new();
56 queue.push_back(name);
57 visited.insert(name);
58
59 while let Some(current) = queue.pop_front() {
60 component.push(current);
61 if let Some(neighbors) = adj.get(current) {
62 for &neighbor in neighbors {
63 if visited.insert(neighbor) {
64 queue.push_back(neighbor);
65 }
66 }
67 }
68 }
69 component.sort();
70 components.push(component);
71 }
72
73 components.sort_by_key(|c| std::cmp::Reverse(c.len()));
74
75 let isolated: Vec<IsolatedProblem> = types
76 .iter()
77 .copied()
78 .filter(|name| adj.get(name).is_some_and(|n| n.is_empty()))
79 .map(|name| {
80 let variants = graph.variants_for(name);
81 let variant_complexities = variants
82 .iter()
83 .map(|v| {
84 let c = graph.variant_complexity(name, v).map(|e| e.to_string());
85 (v.clone(), c)
86 })
87 .collect();
88 IsolatedProblem {
89 name,
90 num_variants: variants.len(),
91 variant_complexities,
92 }
93 })
94 .collect();
95
96 ConnectivityReport {
97 total_types: types.len(),
98 total_reductions: graph.num_reductions(),
99 isolated,
100 components,
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum UnreachableReason {
107 InP,
109 Intermediate,
111 Orphan,
113 MissingProofChain,
115}
116
117#[derive(Debug, Clone)]
119pub struct UnreachableProblem {
120 pub name: &'static str,
121 pub reason: UnreachableReason,
122 pub outgoing_count: usize,
123 pub incoming_count: usize,
124}
125
126#[derive(Debug, Clone)]
128pub struct ReachabilityReport {
129 pub total_types: usize,
131 pub reachable: BTreeMap<&'static str, usize>,
133 pub unreachable: Vec<UnreachableProblem>,
135}
136
137impl ReachabilityReport {
138 pub fn missing_proof_chains(&self) -> Vec<&UnreachableProblem> {
140 self.unreachable
141 .iter()
142 .filter(|p| p.reason == UnreachableReason::MissingProofChain)
143 .collect()
144 }
145}
146
147pub fn check_reachability_from_3sat(graph: &ReductionGraph) -> ReachabilityReport {
151 const SOURCE: &str = "KSatisfiability";
152
153 let mut types = graph.problem_types();
154 types.sort();
155
156 let mut adj: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new();
158 for &name in &types {
159 adj.entry(name).or_default();
160 for edge in graph.outgoing_reductions(name) {
161 adj.entry(name).or_default().insert(edge.target_name);
162 }
163 }
164
165 let mut reachable: BTreeMap<&'static str, usize> = BTreeMap::new();
167 let mut queue: std::collections::VecDeque<(&str, usize)> = std::collections::VecDeque::new();
168 reachable.insert(SOURCE, 0);
169 queue.push_back((SOURCE, 0));
170
171 while let Some((current, hops)) = queue.pop_front() {
172 if let Some(neighbors) = adj.get(current) {
173 for &neighbor in neighbors {
174 if !reachable.contains_key(neighbor) {
175 reachable.insert(neighbor, hops + 1);
176 queue.push_back((neighbor, hops + 1));
177 }
178 }
179 }
180 }
181
182 let p_time_checks: &[(&str, Option<(&str, &str)>)] = &[
184 ("MaximumMatching", None),
185 ("KSatisfiability", Some(("k", "K2"))),
186 ("KColoring", Some(("graph", "SimpleGraph"))),
187 ];
188
189 let intermediate_names: &[&str] = &["Factoring"];
190
191 let mut unreachable_problems: Vec<UnreachableProblem> = Vec::new();
192
193 for &name in &types {
194 if reachable.contains_key(name) {
195 continue;
196 }
197
198 let out_count = graph.outgoing_reductions(name).len();
199 let in_count = graph.incoming_reductions(name).len();
200
201 if out_count == 0 && in_count == 0 {
203 unreachable_problems.push(UnreachableProblem {
204 name,
205 reason: UnreachableReason::Orphan,
206 outgoing_count: 0,
207 incoming_count: 0,
208 });
209 continue;
210 }
211
212 let is_p = p_time_checks.iter().any(|(pname, variant_check)| {
214 if *pname != name {
215 return false;
216 }
217 match variant_check {
218 None => true,
219 Some((key, val)) => {
220 let variants = graph.variants_for(name);
221 variants.len() == 1 && variants[0].get(*key).map(|s| s.as_str()) == Some(*val)
222 }
223 }
224 });
225 if is_p {
226 unreachable_problems.push(UnreachableProblem {
227 name,
228 reason: UnreachableReason::InP,
229 outgoing_count: out_count,
230 incoming_count: in_count,
231 });
232 continue;
233 }
234
235 if intermediate_names.contains(&name) {
237 unreachable_problems.push(UnreachableProblem {
238 name,
239 reason: UnreachableReason::Intermediate,
240 outgoing_count: out_count,
241 incoming_count: in_count,
242 });
243 continue;
244 }
245
246 unreachable_problems.push(UnreachableProblem {
248 name,
249 reason: UnreachableReason::MissingProofChain,
250 outgoing_count: out_count,
251 incoming_count: in_count,
252 });
253 }
254
255 ReachabilityReport {
256 total_types: types.len(),
257 reachable,
258 unreachable: unreachable_problems,
259 }
260}
261
262#[cfg(test)]
263#[path = "../unit_tests/rules/analysis.rs"]
264mod tests;