Skip to content

Commit bc0458e

Browse files
GiggleLiuclaude
andcommitted
test: add integration test for dominated rule detection (#193)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5396e98 commit bc0458e

1 file changed

Lines changed: 143 additions & 1 deletion

File tree

src/unit_tests/rules/analysis.rs

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::expr::Expr;
2-
use crate::rules::analysis::{compare_overhead, ComparisonStatus};
2+
use crate::rules::analysis::{compare_overhead, find_dominated_rules, ComparisonStatus};
3+
use crate::rules::graph::ReductionGraph;
34
use crate::rules::registry::ReductionOverhead;
45

56
// --- Polynomial normalization + comparison tests ---
@@ -191,3 +192,144 @@ fn test_compare_overhead_multi_field_all_smaller() {
191192
]);
192193
assert_eq!(compare_overhead(&prim, &comp), ComparisonStatus::Dominated);
193194
}
195+
196+
// --- Integration tests: find_dominated_rules ---
197+
198+
use std::collections::BTreeMap;
199+
200+
#[test]
201+
fn test_find_dominated_rules_returns_known_set() {
202+
let graph = ReductionGraph::new();
203+
let (dominated, unknown) = find_dominated_rules(&graph);
204+
205+
// Print for debugging
206+
eprintln!("Dominated rules ({}):", dominated.len());
207+
for rule in &dominated {
208+
let path_str: String = rule
209+
.dominating_path
210+
.steps
211+
.iter()
212+
.map(|s| s.to_string())
213+
.collect::<Vec<_>>()
214+
.join(" -> ");
215+
eprintln!(
216+
" {} -> {} dominated by [{}]",
217+
rule.source_name, rule.target_name, path_str,
218+
);
219+
}
220+
eprintln!("\nUnknown comparisons ({}):", unknown.len());
221+
for u in &unknown {
222+
eprintln!(
223+
" {} -> {}: {}",
224+
u.source_name, u.target_name, u.reason,
225+
);
226+
}
227+
228+
// ── Allow-list of expected dominated rules ──
229+
// Keyed by (source_name, target_name).
230+
// This list must be updated when new reductions are added.
231+
let allowed: std::collections::HashSet<(&str, &str)> = [
232+
// Composite through CircuitSAT → ILP is better
233+
("Factoring", "ILP"),
234+
// K3-SAT → QUBO via SAT → CircuitSAT → SpinGlass chain
235+
("KSatisfiability", "QUBO"),
236+
// Cast-composed: K2/K3 → KN → Satisfiability
237+
("KSatisfiability", "Satisfiability"),
238+
// MIS → MVC → ILP is better than direct MIS → ILP
239+
("MaximumIndependentSet", "ILP"),
240+
// Variant cast composed: SimpleGraph/One → KingsSubgraph/One → KingsSubgraph/i32
241+
("MaximumIndependentSet", "MaximumIndependentSet"),
242+
// MIS → MVC → QUBO is better than direct MIS → QUBO
243+
("MaximumIndependentSet", "QUBO"),
244+
// MSP → MIS → ILP is better than direct MSP → ILP
245+
("MaximumSetPacking", "ILP"),
246+
// MVC → MIS → ILP is better than direct MVC → ILP
247+
("MinimumVertexCover", "ILP"),
248+
// MVC → MIS → QUBO is better than direct MVC → QUBO
249+
("MinimumVertexCover", "QUBO"),
250+
]
251+
.into_iter()
252+
.collect();
253+
254+
// Check: no unexpected dominated rules
255+
for rule in &dominated {
256+
let key = (rule.source_name, rule.target_name);
257+
assert!(
258+
allowed.contains(&key),
259+
"Unexpected dominated rule: {} -> {} (dominated by {})",
260+
rule.source_name,
261+
rule.target_name,
262+
rule.dominating_path
263+
.steps
264+
.iter()
265+
.map(|s| s.to_string())
266+
.collect::<Vec<_>>()
267+
.join(" -> "),
268+
);
269+
}
270+
271+
// Check: no stale entries in allow-list
272+
let found: std::collections::HashSet<(&str, &str)> = dominated
273+
.iter()
274+
.map(|r| (r.source_name, r.target_name))
275+
.collect();
276+
for &key in &allowed {
277+
assert!(
278+
found.contains(&key),
279+
"Allow-list entry {:?} -> {:?} is stale (no longer dominated)",
280+
key.0,
281+
key.1,
282+
);
283+
}
284+
}
285+
286+
#[test]
287+
fn test_ilp_qubo_paths_are_unknown() {
288+
let graph = ReductionGraph::new();
289+
let (_, unknown) = find_dominated_rules(&graph);
290+
291+
// Any path through ILP → QUBO should be reported as Unknown
292+
let ilp_qubo_unknowns: Vec<_> = unknown
293+
.iter()
294+
.filter(|u| u.reason.contains("ILP"))
295+
.collect();
296+
297+
assert!(
298+
!ilp_qubo_unknowns.is_empty(),
299+
"Expected at least one Unknown comparison involving ILP -> QUBO"
300+
);
301+
}
302+
303+
#[test]
304+
fn test_no_duplicate_primitive_rules_per_variant_pair() {
305+
use crate::rules::registry::ReductionEntry;
306+
use std::collections::HashSet;
307+
308+
let mut seen = HashSet::new();
309+
for entry in inventory::iter::<ReductionEntry> {
310+
let src_variant: BTreeMap<String, String> = entry
311+
.source_variant()
312+
.into_iter()
313+
.map(|(k, v)| (k.to_string(), v.to_string()))
314+
.collect();
315+
let dst_variant: BTreeMap<String, String> = entry
316+
.target_variant()
317+
.into_iter()
318+
.map(|(k, v)| (k.to_string(), v.to_string()))
319+
.collect();
320+
let key = (
321+
entry.source_name,
322+
src_variant,
323+
entry.target_name,
324+
dst_variant,
325+
);
326+
assert!(
327+
seen.insert(key.clone()),
328+
"Duplicate primitive rule: {} {:?} -> {} {:?}",
329+
key.0,
330+
key.1,
331+
key.2,
332+
key.3,
333+
);
334+
}
335+
}

0 commit comments

Comments
 (0)