Implement 6 problem-to-QUBO reductions (Issue #18)#29
Conversation
- Add scripts/generate_qubo_tests.py using qubogen to generate QUBO ground truth for 7 problem types: MaxCut, VertexCovering, IndependentSet, Coloring, SetPacking, KSatisfiability (2-SAT), and ILP - Add scripts/ Python project managed by uv (pyproject.toml, uv.lock) - Add tests/data/qubo/*.json with brute-force optimal solutions - Add `make qubo-testdata` target to regenerate test data - Update .gitignore for .venv/ Ref #18 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #29 +/- ##
==========================================
+ Coverage 97.21% 97.27% +0.05%
==========================================
Files 160 172 +12
Lines 25169 25778 +609
==========================================
+ Hits 24469 25076 +607
- Misses 700 702 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
[action] |
Implement three problem-to-QUBO reductions with closed-loop unit tests: - IndependentSet → QUBO: penalty formulation with P = 1 + Σw_i - VertexCovering → QUBO: constraint penalty for uncovered edges - MaxCut → QUBO: unconstrained cut maximization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement three more problem-to-QUBO reductions with closed-loop tests: - KColoring → QUBO: one-hot encoding with n*K variables - SetPacking → QUBO: IS on intersection graph structure - KSatisfiability(K=2) → QUBO: Max-2-SAT penalty formulation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement binary ILP to QUBO reduction with slack variables for inequality constraints. Feature-gated behind `ilp`. Supports Maximize, Minimize, and equality/inequality constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 7 integration tests comparing Rust QUBO reductions against Python-generated ground truth JSON datasets for IS, VC, MaxCut, KColoring, SetPacking, KSatisfiability, and ILP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Demonstrate all 7 problem-to-QUBO reductions with practical stories: wireless towers (IS), security cameras (VC), network partitioning (MaxCut), map coloring, delivery zones (SP), switch config (2-SAT), and project selection (ILP). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 7 QUBO reduction theorems with step-by-step mathematical proofs (IS, VC, MaxCut, KColoring, SetPacking, 2-SAT, ILP → QUBO) - Update QUBO definition to clarify upper-triangular matrix convention - Update problem definitions with "Reduces to: QUBO" cross-references - Add 7 rows to the summary table - Add Acknowledgments section to README crediting ProblemReductions.jl, UnitDiskMapping.jl, and qubogen - Fix graph path-finding tests for new QUBO reduction edges - Update .gitignore for pkgref/ reference packages directory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…age tests - Fix ILP→QUBO slack variable signs (Le→+1, Ge→-1) and count formula (ceil(log2(range+1)) to represent all valid slack values) - Remove redundant MaxCut→QUBO reduction (reachable via MaxCut→SpinGlass→QUBO) - Add round-trip tests covering ILP Ge/Le slack branches, KColoring reversed edge ordering, and KSat reversed variable ordering Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds multiple problem→QUBO reductions to the reduction registry, along with unit + integration tests (including ground-truth JSON fixtures), a runnable example, and accompanying documentation/paper updates for Issue #18.
Changes:
- Implement new reductions to
QUBO<f64>for IndependentSet, VertexCovering, KColoring, SetPacking, KSatisfiability(K=2), and (feature-gated) binary ILP. - Add unit tests per reduction plus integration tests that validate solutions against ground-truth JSON instances (generated via
scripts/generate_qubo_tests.py). - Update docs/paper, reduction graph artifacts, repo tooling (Makefile target + Python scripts), and acknowledgments.
Reviewed changes
Copilot reviewed 33 out of 35 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/rules/mod.rs |
Registers and re-exports the new QUBO reductions (and ILP→QUBO behind feature flag). |
src/rules/independentset_qubo.rs |
Implements IndependentSet→QUBO reduction (penalty method). |
src/rules/vertexcovering_qubo.rs |
Implements VertexCovering→QUBO reduction (edge-violation penalty). |
src/rules/coloring_qubo.rs |
Implements KColoring→QUBO reduction using one-hot encoding + edge penalties. |
src/rules/setpacking_qubo.rs |
Implements SetPacking→QUBO reduction (intersection-graph IS form). |
src/rules/ksatisfiability_qubo.rs |
Implements KSatisfiability(K=2)→QUBO clause-penalty gadget. |
src/rules/ilp_qubo.rs |
Implements binary ILP→QUBO (adds slack bits; builds quadratic penalty form). |
src/unit_tests/rules/independentset_qubo.rs |
Unit tests for IS→QUBO closed-loop correctness + size metadata. |
src/unit_tests/rules/vertexcovering_qubo.rs |
Unit tests for VC→QUBO on common small graphs + size metadata. |
src/unit_tests/rules/coloring_qubo.rs |
Unit tests for KColoring→QUBO (valid colorings count + edge-order branch). |
src/unit_tests/rules/setpacking_qubo.rs |
Unit tests for SetPacking→QUBO across overlap scenarios + size metadata. |
src/unit_tests/rules/ksatisfiability_qubo.rs |
Unit tests for 2-SAT→QUBO including swap-branch coverage. |
src/unit_tests/rules/ilp_qubo.rs |
Unit tests for ILP→QUBO across sense/constraint types + slack branches. |
src/unit_tests/rules/graph.rs |
Updates reduction-graph path tests to reflect new direct IS→QUBO edge + shortest-path APIs. |
tests/suites/reductions.rs |
Adds integration tests that load ground-truth JSON and validate extracted solutions. |
tests/data/qubo/independentset_to_qubo.json |
Ground-truth QUBO fixture for IndependentSet instance. |
tests/data/qubo/vertexcovering_to_qubo.json |
Ground-truth QUBO fixture for VertexCovering instance. |
tests/data/qubo/coloring_to_qubo.json |
Ground-truth QUBO fixture for Coloring instance. |
tests/data/qubo/setpacking_to_qubo.json |
Ground-truth QUBO fixture for SetPacking instance. |
tests/data/qubo/ksatisfiability_to_qubo.json |
Ground-truth QUBO fixture for KSatisfiability(2) instance. |
tests/data/qubo/ilp_to_qubo.json |
Ground-truth QUBO fixture for ILP instance. |
examples/qubo_reductions.rs |
Adds an example program demonstrating the six reductions end-to-end. |
scripts/generate_qubo_tests.py |
Adds Python generator to produce ground-truth QUBO JSON via qubogen. |
scripts/pyproject.toml |
Declares Python deps for script execution via uv. |
scripts/uv.lock |
Locks Python dependencies for reproducible test-data generation. |
scripts/.python-version |
Pins Python version for script environment. |
docs/paper/reductions.typ |
Adds/updates formal reduction definitions, theorems, and proofs for QUBO reductions. |
docs/paper/reduction_graph.json |
Updates paper’s reduction graph artifact with new nodes/edges. |
docs/plans/2026-02-08-qubo-reductions-plan.md |
Adds implementation plan document for the work. |
README.md |
Adds acknowledgments referencing inspiration/test-data sources. |
Makefile |
Adds qubo-testdata target to regenerate ground-truth JSON via uv. |
.gitignore |
Ignores Python virtualenv and reference package directory. |
.claude/skills/issue-to-pr.md |
Updates internal workflow documentation around planning/brainstorming. |
.claude/rules/adding-reductions.md |
Documents expected workflow for adding reductions (tests, examples, docs). |
.claude/CLAUDE.md |
Notes make qubo-testdata and documents new repo directories. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Comparison::Le => { | ||
| // Ax <= b → Ax + s = b, s ∈ {0, ..., floor(b)} | ||
| let slack_range = constraint.rhs; | ||
| if slack_range > 0.0 { | ||
| slack_sizes[k] = (slack_range + 1.0).log2().ceil() as usize; | ||
| } | ||
| } | ||
| Comparison::Ge => { | ||
| // Ax >= b → Ax - s = b, s ∈ {0, ..., sum(a) - b} | ||
| let sum_a: f64 = constraint.terms.iter().map(|&(_, c)| c).sum(); | ||
| let slack_range = sum_a - constraint.rhs; | ||
| if slack_range > 0.0 { | ||
| slack_sizes[k] = (slack_range + 1.0).log2().ceil() as usize; | ||
| } | ||
| } |
There was a problem hiding this comment.
ILP→QUBO slack sizing assumes coefficients are nonnegative: for Le it uses slack_range = rhs, and for Ge it uses slack_range = sum_a - rhs where sum_a is the raw sum of coefficients. This is incorrect when constraints contain negative coefficients (which ILP supports elsewhere), and can under-allocate slack bits, making feasible ILPs unsatisfiable in the QUBO encoding. Compute min_lhs/max_lhs over x∈{0,1}^n (e.g., max_lhs = Σ max(0,a_i), min_lhs = Σ min(0,a_i)) and derive slack ranges as rhs - min_lhs for Le and max_lhs - rhs for Ge (with appropriate feasibility checks).
src/rules/ilp_qubo.rs
Outdated
|
|
||
| for (k, constraint) in self.constraints.iter().enumerate() { | ||
| for &(var, coef) in &constraint.terms { | ||
| a_dense[k][var] = coef; |
There was a problem hiding this comment.
When building a_dense, coefficients for a variable are assigned with a_dense[k][var] = coef. If the constraint term list contains the same var multiple times, later entries overwrite earlier ones instead of being summed, changing the meaning of the constraint in the QUBO. Consider accumulating (+=) or normalizing/deduplicating terms before constructing the dense row.
| a_dense[k][var] = coef; | |
| // Accumulate coefficients in case the same variable appears multiple times | |
| a_dense[k][var] += coef; |
tests/suites/reductions.rs
Outdated
| let weights: Vec<i32> = data.source.weights.iter().map(|&w| w as i32).collect(); | ||
| let sp = SetPacking::with_weights(data.source.sets, weights); | ||
| let reduction = ReduceTo::<QUBO>::reduce_to(&sp); |
There was a problem hiding this comment.
This converts JSON weights from f64 to i32 via as, which truncates (e.g. 1.5→1) and can change which SetPacking solution is optimal relative to the ground truth data. Either store integer weights in the JSON, scale them to integers (e.g. multiply by a common factor), or instantiate SetPacking<f64> / a float-weighted reduction so the test uses the same weights as the ground truth.
src/unit_tests/rules/ilp_qubo.rs
Outdated
| fn test_ilp_to_qubo_ge_with_slack() { | ||
| // Ge constraint with slack_range > 1 to exercise slack variable code path. | ||
| // 3 vars: minimize x0 + x1 + x2 | ||
| // s.t. x0 + x1 + x2 >= 1 (sum_a=3, b=1, slack_range=2, ns=ceil(log2(2))=1) |
There was a problem hiding this comment.
The comment here is out of date with the updated slack-bit formula (ceil(log2(slack_range + 1))). For slack_range=2 the correct value is ceil(log2(3))=2, not ceil(log2(2))=1. Please update the comment to match the implementation so the test remains self-explanatory.
| // s.t. x0 + x1 + x2 >= 1 (sum_a=3, b=1, slack_range=2, ns=ceil(log2(2))=1) | |
| // s.t. x0 + x1 + x2 >= 1 (sum_a=3, b=1, slack_range=2, ns=ceil(log2(slack_range + 1))=ceil(log2(3))=2) |
scripts/generate_qubo_tests.py
Outdated
| edges = [(0, 1), (1, 2), (2, 3), (0, 3)] | ||
| n_nodes = 4 | ||
| penalty = 8.0 | ||
| g = qubogen.Graph(edges=np.array(edges), n_nodes=n_nodes) |
There was a problem hiding this comment.
Variable g is not used.
| g = qubogen.Graph(edges=np.array(edges), n_nodes=n_nodes) |
scripts/generate_qubo_tests.py
Outdated
| """ | ||
|
|
||
| import json | ||
| import os |
There was a problem hiding this comment.
Import of 'os' is not used.
| import os |
Add Glover, Kochenberger & Du (2019) "Quantum Bridge Analytics I" as primary reference for QUBO formulations. Cite Lucas (2014) for IS, VC, KColoring, and ILP reductions. Update summary table citations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 6 QUBO reductions from "Trivial" to new "Penalty-Method QUBO Reductions" section. Add introductory paragraph explaining the penalty method with label for cross-referencing. Each proof now references the shared penalty-method description. Remove gray fill from summary table for QUBO rows. Trivial: complement/isomorphism (IS↔VC, IS↔SP, VC→SC, Matching→SP, SG↔QUBO) Penalty-method: IS→QUBO, VC→QUBO, KColoring→QUBO, SP→QUBO, 2SAT→QUBO, ILP→QUBO Non-trivial: gadget-based (SAT→IS, SAT→3-Coloring, etc.) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make SetPacking→QUBO reduction generic over weight type W: Into<f64> (supports both i32 and f64 weights, removing the f64→i32 truncation) - Fix ILP slack range to use min_lhs/max_lhs for negative coefficients - Fix duplicate variable accumulation (= → +=) in ILP constraint matrix - Remove unused import and variable in Python test generator - Update test comments to match corrected formulas Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Implements 6 problem-to-QUBO reductions for Issue #18, with full test coverage, documentation, and an example program. MaxCut→QUBO was removed as redundant (reachable via MaxCut↔SpinGlass↔QUBO).
Reductions added
Bug fixes
ceil(log2(range))toceil(log2(range + 1))to represent all valid slack values 0..rangeFiles added/modified
src/rules/{independentset,vertexcovering,coloring,setpacking,ksatisfiability,ilp}_qubo.rs— 6 reduction implementationssrc/unit_tests/rules/*_qubo.rs— unit tests (closed-loop + edge cases + coverage for slack/swap branches)tests/suites/reductions.rs— 6 integration tests against ground truth JSON from qubogenexamples/qubo_reductions.rs— example program with practical stories for all 6 reductionsdocs/paper/reductions.typ— 6 new theorems with step-by-step mathematical proofsREADME.md— added Acknowledgments section (ProblemReductions.jl, UnitDiskMapping.jl, qubogen)src/unit_tests/rules/graph.rs— fix path-finding tests for new QUBO edges in reduction graphRemoved (redundant)
Test plan
make test— 1622 tests pass (1512 unit + 61 integration + 49 doc)make clippy— no warningsmake paper— Typst paper compiles with new theoremscargo run --example qubo_reductions --features ilp— example runs successfully🤖 Generated with Claude Code