feat: add 11 Tier 1a + 1b medium-confidence reduction rules (#770)#804
feat: add 11 Tier 1a + 1b medium-confidence reduction rules (#770)#804
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…st reduction (#241) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ion (#424) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#109) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #804 +/- ##
==========================================
+ Coverage 97.89% 97.91% +0.02%
==========================================
Files 651 673 +22
Lines 71463 72756 +1293
==========================================
+ Hits 69959 71240 +1281
- Misses 1504 1516 +12 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a batch of new Tier 1a/1b reduction rules (with tests, canonical examples, and paper entries) to expand the reduction graph and documented proof corpus across graph, set, and misc/algebraic models.
Changes:
- Implement 11 new reduction rules and register them in the rules module + canonical example spec aggregator.
- Add unit tests for each new rule (closed-loop/structure/extraction checks).
- Extend a few models with new public getters / variant declarations needed for overhead and reductions, and update the Typst paper with new reduction entries.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/rules/subsetsum_capacityassignment.rs | Implements SubsetSum → CapacityAssignment reduction + canonical example + tests hook. |
| src/unit_tests/rules/subsetsum_capacityassignment.rs | Adds closed-loop, structure, NO-instance, and monotonicity tests for SubsetSum → CapacityAssignment. |
| src/rules/rootedtreearrangement_rootedtreestorageassignment.rs | Implements RootedTreeArrangement → RootedTreeStorageAssignment reduction + canonical example + tests hook. |
| src/unit_tests/rules/rootedtreearrangement_rootedtreestorageassignment.rs | Adds closed-loop, structure, UNSAT, and extraction tests for RTA → RTSA. |
| src/rules/partitionintopathsoflength2_boundedcomponentspanningforest.rs | Implements PPL2 → BCSF reduction + canonical example + tests hook. |
| src/unit_tests/rules/partitionintopathsoflength2_boundedcomponentspanningforest.rs | Adds closed-loop, UNSAT, and extraction tests for PPL2 → BCSF. |
| src/rules/partition_subsetsum.rs | Implements Partition → SubsetSum reduction + canonical example + tests hook. |
| src/unit_tests/rules/partition_subsetsum.rs | Adds closed-loop, structure, odd-sum, and equal-elements tests for Partition → SubsetSum. |
| src/rules/paintshop_qubo.rs | Implements PaintShop → QUBO reduction + canonical example + tests hook. |
| src/unit_tests/rules/paintshop_qubo.rs | Adds closed-loop, matrix-structure, and optimal-value tests for PaintShop → QUBO (+ example-db spec check). |
| src/rules/minimumvertexcover_minimumhittingset.rs | Implements MVC(One) → MinimumHittingSet reduction + canonical example + tests hook. |
| src/unit_tests/rules/minimumvertexcover_minimumhittingset.rs | Adds closed-loop, structure, and extraction tests for MVC → HS. |
| src/rules/minimumvertexcover_ensemblecomputation.rs | Implements MVC → EnsembleComputation reduction + canonical example + tests hook. |
| src/unit_tests/rules/minimumvertexcover_ensemblecomputation.rs | Adds structure and extraction-focused tests for MVC → EC. |
| src/rules/longestcommonsubsequence_maximumindependentset.rs | Implements LCS → MIS reduction + canonical example + tests hook. |
| src/unit_tests/rules/longestcommonsubsequence_maximumindependentset.rs | Adds closed-loop, structure, and extraction tests for LCS → MIS (k=2..4). |
| src/rules/kclique_balancedcompletebipartitesubgraph.rs | Implements KClique → BCBS reduction + canonical example + tests hook. |
| src/unit_tests/rules/kclique_balancedcompletebipartitesubgraph.rs | Adds closed-loop and several parameter-edge-case tests for KClique → BCBS. |
| src/rules/kcoloring_twodimensionalconsecutivesets.rs | Implements K3-Coloring → TDCS reduction + canonical example + tests hook. |
| src/unit_tests/rules/kcoloring_twodimensionalconsecutivesets.rs | Adds closed-loop and extraction-validity tests for K3-Coloring → TDCS. |
| src/rules/hamiltoniancircuit_longestcircuit.rs | Implements HamiltonianCircuit → LongestCircuit reduction + canonical example + tests hook. |
| src/unit_tests/rules/hamiltoniancircuit_longestcircuit.rs | Adds closed-loop, structure, and extraction tests for HC → LongestCircuit. |
| src/rules/mod.rs | Registers the new rule modules and adds their canonical example specs into the aggregator. |
| src/unit_tests/rules/analysis.rs | Updates dominated-rules expectation set to account for the new KClique → BCBS path. |
| src/models/misc/paintshop.rs | Adds public getters needed by the PaintShop → QUBO reduction (sequence indices + parity). |
| src/models/misc/longest_common_subsequence.rs | Adds cross_frequency_product() getter for LCS → MIS overhead accounting. |
| src/models/graph/minimum_vertex_cover.rs | Adds One weight variant support + declares MVC<SimpleGraph,One> variant. |
| docs/paper/reductions.typ | Adds Typst paper entries/examples for the newly introduced reductions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Bound K' = K - |E|; saturate at 0 to avoid underflow | ||
| let bound = self.bound().saturating_sub(num_edges); | ||
|
|
There was a problem hiding this comment.
saturating_sub(num_edges) makes the reduction unsound when K < |E|: the source is always infeasible (each edge contributes distance ≥ 1) but the target can become feasible with bound = 0 (e.g., a tree graph with K = |E| - 1). Use checked_sub; on underflow, return a target instance guaranteed unsatisfiable (e.g., a small fixed gadget with minimum extension cost ≥ 1) rather than clamping to 0.
| // Bound K' = K - |E|; saturate at 0 to avoid underflow | |
| let bound = self.bound().saturating_sub(num_edges); | |
| // Bound K' = K - |E|. If this underflows (K < |E|), the source instance | |
| // is infeasible (each edge contributes at least 1 to the arrangement | |
| // cost). In that case, return a fixed gadget instance that is | |
| // guaranteed infeasible for the target problem as well. | |
| let bound = match self.bound().checked_sub(num_edges) { | |
| Some(b) => b, | |
| None => { | |
| // Gadget: universe {0,1,2} with all 2-element subsets and bound 0. | |
| // For any rooted tree on three vertices, at least one pair has | |
| // distance 2, so at least one subset has extension cost >= 1. | |
| // Thus the minimum total extension cost is >= 1, making this | |
| // instance infeasible for bound 0. | |
| let gadget_n = 3; | |
| let gadget_subsets = vec![vec![0, 1], vec![1, 2], vec![0, 2]]; | |
| let target = RootedTreeStorageAssignment::new(gadget_n, gadget_subsets, 0); | |
| return ReductionRootedTreeArrangementToRootedTreeStorageAssignment { | |
| target, | |
| num_vertices: gadget_n, | |
| }; | |
| } | |
| }; |
| let n = self.num_vertices(); | ||
| let q = n / 3; | ||
|
|
||
| let target = BoundedComponentSpanningForest::new( | ||
| SimpleGraph::new(n, self.graph().edges()), | ||
| vec![1i32; n], // unit weights | ||
| q, // K = |V|/3 | ||
| 3, // B = 3 | ||
| ); |
There was a problem hiding this comment.
When the source has n = 0 vertices, q = n/3 becomes 0 and BoundedComponentSpanningForest::new(..., max_components = 0, ...) will panic because it requires max_components >= 1. Handle the empty-graph case explicitly (e.g., set max_components = 1 with an empty graph/weights) so the reduction is total and preserves satisfiability.
src/rules/paintshop_qubo.rs
Outdated
| let mut matrix = vec![vec![0.0f64; n]; n]; | ||
|
|
||
| // For each adjacent pair in the sequence | ||
| for pos in 0..(seq_len - 1) { |
There was a problem hiding this comment.
for pos in 0..(seq_len - 1) will underflow when seq_len == 0 (PaintShop currently allows an empty sequence with 0 cars). Use 0..seq_len.saturating_sub(1) or an early-return for seq_len < 2 to avoid panics and keep the reduction defined on edge cases.
| for pos in 0..(seq_len - 1) { | |
| for pos in 0..seq_len.saturating_sub(1) { |
| let vertex_groups: Vec<usize> = target_solution[..self.num_vertices].to_vec(); | ||
|
|
||
| // Collect all distinct group indices used by all symbols and compress to 0..k-1 | ||
| let mut used_groups: Vec<usize> = target_solution.to_vec(); | ||
| used_groups.sort(); | ||
| used_groups.dedup(); | ||
|
|
||
| let mut group_to_color = vec![0usize; self.num_vertices + self.edges.len()]; | ||
| for (color, &group) in used_groups.iter().enumerate() { | ||
| if group < group_to_color.len() { | ||
| group_to_color[group] = color; | ||
| } | ||
| } | ||
|
|
||
| vertex_groups.iter().map(|&g| group_to_color[g]).collect() | ||
| } |
There was a problem hiding this comment.
extract_solution can output colors > 2 when the TDCS witness assigns graph vertices to more than 3 distinct groups (which is possible even when the source is 3-colorable). Since the source expects exactly 3 colors, this breaks witness extraction for some valid target witnesses. Consider mirroring TDCS’s internal label-compression and then mapping vertex dense labels modulo 3 (or otherwise deriving a 3-coloring) so every satisfying target witness extracts to a satisfying K3-coloring.
| // Issue example: MIS solution {v2, v3, v5} gives LCS "BAC" (length 3). | ||
| // Match nodes (ordered by character): | ||
| // c=A(0): v0=(0,1), v1=(0,3), v3=(2,1), v4=(2,3) | ||
| // c=B(1): v2=(1,0) | ||
| // c=C(2): v5=(3,2) | ||
| // MIS {v2, v3, v5} => positions B@(1,0), A@(2,1), C@(3,2) |
There was a problem hiding this comment.
The canonical example comments mislabel the selected vertices: with the tuple-generation order in this file, index 2 is an A-node (2,1) and index 4 is the B-node (1,0), but the comment says otherwise. This makes it hard to audit the example/witness mapping; please update the comment so indices/tuples match the actual construction.
| // Issue example: MIS solution {v2, v3, v5} gives LCS "BAC" (length 3). | |
| // Match nodes (ordered by character): | |
| // c=A(0): v0=(0,1), v1=(0,3), v3=(2,1), v4=(2,3) | |
| // c=B(1): v2=(1,0) | |
| // c=C(2): v5=(3,2) | |
| // MIS {v2, v3, v5} => positions B@(1,0), A@(2,1), C@(3,2) | |
| // Issue example: MIS solution {v2, v4, v5} gives LCS "BAC" (length 3). | |
| // Match nodes (ordered by character): | |
| // c=A(0): v0=(0,1), v1=(0,3), v2=(2,1), v3=(2,3) | |
| // c=B(1): v4=(1,0) | |
| // c=C(2): v5=(3,2) | |
| // MIS {v2, v4, v5} => positions B@(1,0), A@(2,1), C@(3,2) |
| .target() | ||
| .try_into() | ||
| .expect("SubsetSum target must fit in u64 for CapacityAssignment reduction"); | ||
| let delay_budget = total_sum - target_val; |
There was a problem hiding this comment.
delay_budget is computed as total_sum - target_val on u64, which will underflow/panic (debug) or wrap (release) when target_val > total_sum (a valid unsatisfiable SubsetSum instance). Use checked_sub/saturating_sub and pick semantics that keep the reduction sound for B > S (e.g., treat negative budgets as 0 so the optimal cost cannot equal B).
| let delay_budget = total_sum - target_val; | |
| // Use saturating subtraction to avoid underflow when target_val > total_sum. | |
| // In that case, treat the delay budget as 0 so the reduction remains sound. | |
| let delay_budget = total_sum.saturating_sub(target_val); |
| for size in self.sizes() { | ||
| let a_i: u64 = size | ||
| .try_into() | ||
| .expect("SubsetSum element must fit in u64 for CapacityAssignment reduction"); | ||
| cost.push(vec![0, a_i]); | ||
| delay.push(vec![a_i, 0]); |
There was a problem hiding this comment.
This reduction converts BigUint sizes/target to u64 via try_into().expect(...). Since SubsetSum is explicitly arbitrary-precision, this makes the reduction panic on perfectly valid instances. Consider either (a) changing the target model to support larger integers for this rule, or (b) explicitly rejecting/encoding out-of-range instances in a way that preserves correctness (instead of panicking).
- RTA→RTSA: use checked_sub with infeasible gadget instead of saturating_sub - PIPL2→BCSF: handle n=0 empty graph (max_components must be >= 1) - PaintShop→QUBO: use saturating_sub to prevent underflow on empty sequence - KColoring→TDCS: use HashMap for group_to_color mapping, remove unused edges field - LCS→MIS: fix mislabeled match-node indices in example comments - SubsetSum→CapAssign: use saturating_sub for delay_budget underflow - Paper: fix #n-vertex Typst variable error (escape hyphen) - Add edge-case tests for RTA underflow, empty PaintShop, SubsetSum target>sum - Merge main to resolve conflicts with CosineProductIntegration/ProductionPlanning Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge main branch to pick up new models (IntegerKnapsack, FeasibleBasisExtension, QuadraticDiophantineEquations, etc.). Resolved conflict in references.bib by keeping entries from both branches. Applied rustfmt to fix formatting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Design issue: MVC → EnsembleComputation (#204) This rule has two fundamental problems that need to be addressed before it can be merged: 1. Trivial upper-bound budget makes the reduction vacuous. 2. Source variant is weighted Recommendation: Remove MVC → EnsembleComputation from this batch PR. The remaining 10 rules are sound and ready to merge. File a separate issue for redesigning this reduction (e.g., by adding an explicit K parameter to the source, or by implementing an aggregate-only edge that binary-searches on K). |
|
Design issue: MVC → EnsembleComputation (#204) — recommend removing from this batch After reviewing the 11 rules in this PR, I found that the MinimumVertexCover → EnsembleComputation reduction has two fundamental design problems. The other 10 rules are sound. Problem 1: Trivial upper-bound budget makes the reduction vacuousThe Garey & Johnson proof (PO9) establishes a tight equivalence:
The key insight is that J depends on K — the vertex cover budget. However, This makes the EC instance always satisfiable for any graph with edges. The reduction becomes one-directional: you can always find a satisfying EC sequence, but the extracted vertex cover is just "some valid cover" — potentially all vertices. You cannot recover the minimum cover through this path, because the budget constraint that encodes optimality has been relaxed away. For comparison, the other feasibility-to-feasibility reductions in this PR (e.g., KClique → BCBS, Partition → SubsetSum) correctly propagate the budget parameter, so the target is satisfiable iff the source is. Problem 2: Source variant is weighted
|
…MVC→EC reduction - EnsembleComputation: Value changed from Or (feasibility) to Min<usize> (minimize sequence length). The budget parameter remains as a search-space bound, but evaluate() now returns the number of steps used rather than just true/false. - MVC→EC reduction: source changed from MinimumVertexCover<SimpleGraph, i32> to MinimumVertexCover<SimpleGraph, One>. The weighted variant was unsound because EC has no weight field. With both sides as Min, the optimal value relationship J* = K* + |E| is tight — no trivial upper bound needed. - Updated paper entries, model tests, and rule tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Update: MVC → EnsembleComputation issue has been fixed in the latest push. Changes made:
All tests pass, paper builds clean. |
- Add `with_default_budget()` and `default_budget()` methods. Default is sum of subset sizes (worst-case without reuse), clamped to at least 1. - CLI: --budget is now optional; omitting it uses the default. - Paper: remove J from problem definition (it's a search-space bound, not a mathematical parameter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Add 11 reduction rules from #770 (Tier 1a + Tier 1b medium-confidence). Each rule includes: implementation, unit tests, example_db entry, and paper entry.
Fixes #200
Fixes #241
Fixes #358
Fixes #387
Fixes #424
Fixes #426
Fixes #109
Fixes #204
Fixes #231
Fixes #437
Fixes #649
Agentic Review
Structural Check
Build: PASS — fmt, clippy, 3655+ tests all pass
Feature Tests
All 11 rules: fully discoverable, reducible, and solvable (where ILP path exists).
pred listpred showpred createpred reducepred solveReview Comments Resolution
rootedtreearrangement_rootedtreestorageassignment.rs:69saturating_subunsound when K < |E|checked_sub+ infeasible gadget; test addedpartitionintopathsoflength2_boundedcomponentspanningforest.rs:62max_componentsto ≥1paintshop_qubo.rs:49seq_len == 0saturating_sub(1); test addedkcoloring_twodimensionalconsecutivesets.rs:67extract_solution% 3longestcommonsubsequence_maximumindependentset.rs:209subsetsum_capacityassignment.rs:74target > sumsaturating_sub; test addedsubsetsum_capacityassignment.rs:58Quality issues noted (low severity, deferred)
i32variant but discards weights