From 5033265b7093afe43479827a18814007486bf259 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 15:52:03 +0800 Subject: [PATCH 1/4] Add plan for #138: [Rule] SubsetSum to Knapsack --- .../plans/2026-03-20-subsetsum-to-knapsack.md | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 docs/plans/2026-03-20-subsetsum-to-knapsack.md diff --git a/docs/plans/2026-03-20-subsetsum-to-knapsack.md b/docs/plans/2026-03-20-subsetsum-to-knapsack.md new file mode 100644 index 00000000..71c6e703 --- /dev/null +++ b/docs/plans/2026-03-20-subsetsum-to-knapsack.md @@ -0,0 +1,263 @@ +# SubsetSum To Knapsack Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add the `SubsetSum -> Knapsack` reduction rule, register a canonical example, and document the rule in the paper so the reduction graph exposes the path `KSatisfiability -> SubsetSum -> Knapsack -> QUBO`. + +**Architecture:** This reduction is a sat-to-opt embedding against the existing optimization `Knapsack` model, not a decision-threshold variant. The implementation maps each subset-sum element to a knapsack item with identical weight and value, uses the subset target as capacity, stores no extra mapping state beyond the target instance, and relies on source-side evaluation after solution extraction to distinguish SAT from UNSAT cases. Because `SubsetSum` uses `BigUint` and `Knapsack` uses `i64`, the reduction must perform checked conversion and fail loudly when the source instance cannot be represented in the target model. + +**Tech Stack:** Rust, cargo test, example-db exports, Typst paper, GitHub PR pipeline helpers. + +--- + +## Batch 1: Rule, Tests, and Canonical Example + +### Task 1: Add the failing rule tests first + +**Files:** +- Create: `src/unit_tests/rules/subsetsum_knapsack.rs` +- Read for reference: `src/unit_tests/rules/sat_maximumindependentset.rs` +- Read for helper APIs: `src/rules/test_helpers.rs` + +**Step 1: Write the failing tests** + +Add focused tests that define the intended behavior before implementing the rule: +- `test_subsetsum_to_knapsack_closed_loop`: + - Build `SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32)`. + - Reduce to `Knapsack`. + - Assert `assert_satisfaction_round_trip_from_optimization_target(&source, &reduction, "SubsetSum->Knapsack closed loop")`. +- `test_subsetsum_to_knapsack_target_structure`: + - Assert `weights == [3, 7, 1, 8, 4, 12, 5]`. + - Assert `values == [3, 7, 1, 8, 4, 12, 5]`. + - Assert `capacity == 15`. + - Assert `num_items == source.num_elements()`. +- `test_subsetsum_to_knapsack_unsat_extracts_non_solution`: + - Build an UNSAT source such as `SubsetSum::new(vec![2u32, 4, 6], 5u32)`. + - Reduce and solve the target with `solve_optimization_problem`. + - Extract the source config and assert `!source.evaluate(&extracted)`. +- `test_subsetsum_to_knapsack_panics_on_i64_overflow`: + - Build a source with one size larger than `i64::MAX` using `BigUint`. + - Assert the reduction panics with a message that mentions conversion to `i64`. +- `#[cfg(feature = "example-db")] test_subsetsum_to_knapsack_canonical_example_spec`: + - Find the rule example spec by id. + - Assert the exported example names are `SubsetSum` and `Knapsack`. + - Assert the example contains at least one witness. + +**Step 2: Run the focused test file to verify RED** + +Run: +```bash +cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture +``` + +Expected: +- Compile or test failure because the rule module and registrations do not exist yet. + +**Step 3: Commit note** + +Do not commit in RED state. Move directly to implementation after confirming the failure is for the missing rule. + +### Task 2: Implement the reduction rule and registration + +**Files:** +- Create: `src/rules/subsetsum_knapsack.rs` +- Modify: `src/rules/mod.rs` +- Read for reference: `src/rules/sat_maximumindependentset.rs` +- Read for model semantics: `src/models/misc/subset_sum.rs` +- Read for model semantics: `src/models/misc/knapsack.rs` + +**Step 1: Write the minimal production code** + +Implement `src/rules/subsetsum_knapsack.rs` with: +- `ReductionSubsetSumToKnapsack { target: Knapsack }` +- `ReductionResult` impl: + - `type Source = SubsetSum` + - `type Target = Knapsack` + - `target_problem()` returns `&self.target` + - `extract_solution()` returns `target_solution.to_vec()` +- `#[reduction(overhead = { num_items = "num_elements" })]` +- `impl ReduceTo for SubsetSum` + - Convert each source size to `i64` with `i64::try_from(...)` + - Use the converted vector for both target weights and target values + - Convert `target()` to `i64` for capacity using the same checked conversion + - Panic with a clear message if any conversion does not fit in `i64` +- `#[cfg(test)] #[path = "../unit_tests/rules/subsetsum_knapsack.rs"] mod tests;` + +Update `src/rules/mod.rs` to register `pub(crate) mod subsetsum_knapsack;` and extend `canonical_rule_example_specs()` with `subsetsum_knapsack::canonical_rule_example_specs()`. + +**Step 2: Run the focused tests to verify GREEN** + +Run: +```bash +cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture +``` + +Expected: +- The new rule tests pass. +- Any failures should now be behavioral, not missing-module errors. + +**Step 3: Refactor if needed** + +Keep the implementation minimal. Only extract a local helper if the conversion logic is duplicated enough to obscure the rule. + +### Task 3: Add the canonical example in the rule module + +**Files:** +- Modify: `src/rules/subsetsum_knapsack.rs` +- Read for reference: `src/rules/knapsack_qubo.rs` +- Read for example helpers: `src/example_db/specs.rs` + +**Step 1: Implement the canonical example builder** + +Inside `#[cfg(feature = "example-db")] pub(crate) fn canonical_rule_example_specs() -> Vec`: +- Reuse the issue’s 7-element canonical instance: + - Source: `SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32)` + - Canonical witness: `source_config = target_config = vec![1, 0, 0, 0, 0, 1, 0]` +- Export it with `rule_example_with_witness::<_, Knapsack>(...)` +- Use id `subsetsum_to_knapsack` + +The witness must be valid in both directions, but it does not need to enumerate all optimal or satisfying assignments. + +**Step 2: Re-run the focused tests** + +Run: +```bash +cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture +``` + +Expected: +- The canonical example test now passes as well. + +### Task 4: Verify the rule and exports before the paper batch + +**Files:** +- Modify if generated and tracked: exported JSON/SVG files touched by the commands below + +**Step 1: Run broader verification for the new rule** + +Run: +```bash +cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --include-ignored +cargo run --features "example-db" --example export_examples +cargo run --example export_graph +cargo run --example export_schemas +``` + +Expected: +- Tests pass. +- Export commands succeed. +- Generated fixtures now contain the new rule example data needed by the paper. + +**Step 2: Inspect the exported example data** + +Check that the exported rule example contains: +- Source problem `SubsetSum` +- Target problem `Knapsack` +- The canonical witness pair with identical source/target configs + +**Step 3: Commit Batch 1** + +Run: +```bash +git add src/rules/subsetsum_knapsack.rs src/rules/mod.rs src/unit_tests/rules/subsetsum_knapsack.rs src/example_db/fixtures/examples.json docs/src/reduction_graph.json docs/src/problem_schemas.json +git commit -m "Implement #138: add SubsetSum to Knapsack reduction" +``` + +If the export paths differ from the expected tracked files, inspect `git status --short` and stage the actual tracked outputs instead of inventing paths. + +## Batch 2: Paper Entry and Final Verification + +### Task 5: Document the rule in the paper + +**Files:** +- Modify: `docs/paper/reductions.typ` +- Read for reference: nearby `SubsetSum` and `Knapsack` problem definitions +- Read for reference: `reduction-rule("KSatisfiability", "SubsetSum", ...)` +- Read for reference: `reduction-rule("Knapsack", "QUBO", ...)` + +**Step 1: Add the reduction-rule entry** + +Insert a new `#let` block and `#reduction-rule("SubsetSum", "Knapsack", ...)` between the existing `KSatisfiability -> SubsetSum` and `Knapsack -> QUBO` sections. + +The paper entry must reflect the implemented semantics: +- State that the reduction maps each element size to both item weight and item value, with capacity equal to the subset target. +- Explain the sat-to-opt correctness condition: SubsetSum is satisfiable iff the target Knapsack optimum reaches value `T`. +- Mention the checked `BigUint -> i64` representation boundary in prose or the worked example if needed, but do not claim the target supports arbitrary precision. +- Use exported example data rather than hardcoded duplicate constants where practical. +- Make the witness semantics explicit: the fixture stores one canonical witness, while multiple satisfying subsets may exist. + +**Step 2: Add a worked example** + +Use the exported 7-element canonical instance and walk through: +- Source set and target value +- Target weights, values, and capacity +- Why the chosen witness reaches value 15 exactly +- Why equality of weights and values makes extracted solutions identical + +**Step 3: Build the paper** + +Run: +```bash +make paper +``` + +Expected: +- Typst compiles successfully. +- No completeness warnings for an undocumented `SubsetSum -> Knapsack` edge remain. + +### Task 6: Final verification, cleanup, and push-ready state + +**Files:** +- Delete after execution: `docs/plans/2026-03-20-subsetsum-to-knapsack.md` + +**Step 1: Run final verification** + +Run: +```bash +make fmt +cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --include-ignored +make clippy +git status --short +``` + +Expected: +- Formatting clean +- Rule-specific tests pass +- Clippy passes +- Only intended tracked files are modified + +**Step 2: Commit paper and generated artifacts** + +Run: +```bash +git add docs/paper/reductions.typ +git add -A +git commit -m "Document #138: add SubsetSum to Knapsack paper entry" +``` + +**Step 3: Remove the plan file after implementation** + +Run: +```bash +git rm docs/plans/2026-03-20-subsetsum-to-knapsack.md +git commit -m "chore: remove plan file after implementation" +``` + +**Step 4: Pre-push summary** + +Before push, prepare the PR implementation summary comment with: +- The new rule module and test coverage +- The canonical example and exported fixture updates +- The paper entry +- Any deviation from the original issue text: + - used optimization Knapsack semantics + - added checked `BigUint -> i64` conversion boundary + +**Step 5: Push** + +Run: +```bash +git push +``` + +Stop if verification reveals unrelated changes or failures. Fix them before pushing. From d06a111d759824f554205e725011dd64bcbdbd84 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 16:04:05 +0800 Subject: [PATCH 2/4] Implement #138: add SubsetSum to Knapsack reduction --- src/rules/mod.rs | 2 + src/rules/subsetsum_knapsack.rs | 67 ++++++++++++++++++++ src/unit_tests/rules/subsetsum_knapsack.rs | 74 ++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 src/rules/subsetsum_knapsack.rs create mode 100644 src/unit_tests/rules/subsetsum_knapsack.rs diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 77f3d894..2243d5bc 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -35,6 +35,7 @@ pub(crate) mod sat_minimumdominatingset; mod spinglass_casts; pub(crate) mod spinglass_maxcut; pub(crate) mod spinglass_qubo; +pub(crate) mod subsetsum_knapsack; #[cfg(test)] pub(crate) mod test_helpers; mod traits; @@ -107,6 +108,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec &Self::Target { + &self.target + } + + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + target_solution.to_vec() + } +} + +#[reduction(overhead = { num_items = "num_elements" })] +impl ReduceTo for SubsetSum { + type Result = ReductionSubsetSumToKnapsack; + + fn reduce_to(&self) -> Self::Result { + let weights = self.sizes().iter().map(biguint_to_i64).collect::>(); + let capacity = biguint_to_i64(self.target()); + + ReductionSubsetSumToKnapsack { + target: Knapsack::new(weights.clone(), weights, capacity), + } + } +} + +fn biguint_to_i64(value: &BigUint) -> i64 { + value.to_i64().unwrap_or_else(|| { + panic!("SubsetSum -> Knapsack reduction requires all sizes and target to fit in i64") + }) +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_rule_example_specs() -> Vec { + use crate::export::SolutionPair; + + vec![crate::example_db::specs::RuleExampleSpec { + id: "subsetsum_to_knapsack", + build: || { + crate::example_db::specs::rule_example_with_witness::<_, Knapsack>( + SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32), + SolutionPair { + source_config: vec![1, 0, 0, 0, 0, 1, 0], + target_config: vec![1, 0, 0, 0, 0, 1, 0], + }, + ) + }, + }] +} + +#[cfg(test)] +#[path = "../unit_tests/rules/subsetsum_knapsack.rs"] +mod tests; diff --git a/src/unit_tests/rules/subsetsum_knapsack.rs b/src/unit_tests/rules/subsetsum_knapsack.rs new file mode 100644 index 00000000..15ae2046 --- /dev/null +++ b/src/unit_tests/rules/subsetsum_knapsack.rs @@ -0,0 +1,74 @@ +use super::*; +use crate::models::misc::{Knapsack, SubsetSum}; +use crate::rules::test_helpers::{ + assert_satisfaction_round_trip_from_optimization_target, solve_optimization_problem, +}; +use crate::traits::Problem; +use num_bigint::BigUint; + +#[test] +fn test_subsetsum_to_knapsack_closed_loop() { + let source = SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32); + let reduction = ReduceTo::::reduce_to(&source); + + assert_satisfaction_round_trip_from_optimization_target( + &source, + &reduction, + "SubsetSum->Knapsack closed loop", + ); +} + +#[test] +fn test_subsetsum_to_knapsack_target_structure() { + let source = SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32); + let reduction = ReduceTo::::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.weights(), &[3, 7, 1, 8, 4, 12, 5]); + assert_eq!(target.values(), &[3, 7, 1, 8, 4, 12, 5]); + assert_eq!(target.capacity(), 15); + assert_eq!(target.num_items(), source.num_elements()); +} + +#[test] +fn test_subsetsum_to_knapsack_unsat_extracts_non_solution() { + let source = SubsetSum::new(vec![2u32, 4, 6], 5u32); + let reduction = ReduceTo::::reduce_to(&source); + + let target_solution = solve_optimization_problem(reduction.target_problem()) + .expect("SubsetSum->Knapsack: optimization target should always have an optimum"); + let extracted = reduction.extract_solution(&target_solution); + + assert!(!source.evaluate(&extracted)); +} + +#[test] +#[should_panic( + expected = "SubsetSum -> Knapsack reduction requires all sizes and target to fit in i64" +)] +fn test_subsetsum_to_knapsack_panics_on_i64_overflow() { + let too_large = BigUint::from(i64::MAX as u64) + BigUint::from(1u32); + let source = SubsetSum::new_unchecked(vec![too_large.clone()], too_large); + + let _ = ReduceTo::::reduce_to(&source); +} + +#[cfg(feature = "example-db")] +#[test] +fn test_subsetsum_to_knapsack_canonical_example_spec() { + let spec = canonical_rule_example_specs() + .into_iter() + .find(|spec| spec.id == "subsetsum_to_knapsack") + .expect("missing canonical SubsetSum -> Knapsack example spec"); + let example = (spec.build)(); + + assert_eq!(example.source.problem, "SubsetSum"); + assert_eq!(example.target.problem, "Knapsack"); + assert_eq!( + example.solutions, + vec![crate::export::SolutionPair { + source_config: vec![1, 0, 0, 0, 0, 1, 0], + target_config: vec![1, 0, 0, 0, 0, 1, 0], + }] + ); +} From a4f339529fb66397b4b0b675ffd0ffcbd2b552ab Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 16:04:08 +0800 Subject: [PATCH 3/4] Document #138: add SubsetSum to Knapsack paper entry --- docs/paper/reductions.typ | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 57127ed8..e3fc21ad 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -3415,8 +3415,7 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], let nproc = x.instance.num_processors let deadlines = x.instance.deadlines let precs = x.instance.precedences - let sample = x.samples.at(0) - let start = sample.config + let start = x.optimal_config let horizon = deadlines.fold(0, (acc, d) => if d > acc { d } else { acc }) let slot-groups = range(horizon).map(slot => range(ntasks).filter(t => start.at(t) == slot)) let tight-tasks = range(ntasks).filter(t => start.at(t) + 1 == deadlines.at(t)) @@ -4278,6 +4277,42 @@ where $P$ is a penalty weight large enough that any constraint violation costs m _Solution extraction._ Discard slack variables: return $bold(x)' [0..n]$. ] +#let ss_kn = load-example("SubsetSum", "Knapsack") +#let ss_kn_sol = ss_kn.solutions.at(0) +#let ss_kn_sizes = ss_kn.source.instance.sizes +#let ss_kn_selected = range(ss_kn_sizes.len()).filter(i => ss_kn_sol.source_config.at(i) == 1) +#let ss_kn_sel_sizes = ss_kn_selected.map(i => ss_kn_sizes.at(i)) +#reduction-rule("SubsetSum", "Knapsack", + example: true, + example-caption: [$n = #ss_kn_sizes.len()$ elements, target $B = #ss_kn.source.instance.target$], + extra: [ + *Step 1 -- Source instance.* The canonical Subset Sum instance has sizes $(#ss_kn_sizes.map(str).join(", "))$ and target $B = #ss_kn.source.instance.target$. + + *Step 2 -- Build Knapsack.* Reuse the same numbers as both weights and values: + $bold(w) = (#ss_kn.target.instance.weights.map(str).join(", "))$, $bold(v) = (#ss_kn.target.instance.values.map(str).join(", "))$, and capacity $C = #ss_kn.target.instance.capacity$. + + *Step 3 -- Verify a witness.* The canonical witness $bold(x) = (#ss_kn_sol.source_config.map(str).join(", "))$ selects $\{#ss_kn_sel_sizes.map(str).join(", ")\}$, so the total selected weight and value are $#ss_kn_sel_sizes.map(str).join(" + ") = #ss_kn.source.instance.target$. + + *Witness semantics.* The fixture stores one canonical witness. Other subsets can also sum to $B$ for this instance, and every such subset maps to an optimal Knapsack solution by the same identity extraction. + ], +)[ + This is the direct special-case embedding between the classical formulations of Subset Sum and Knapsack @karp1972 @garey1979. Given positive integers $a_0, dots, a_(n-1)$ and target $B$, create one knapsack item per element with weight $w_i = a_i$ and value $v_i = a_i$, and set the capacity to $C = B$. The reduction preserves the number of variables/items exactly. In the present library implementation, this embedding applies when all source integers fit in signed 64-bit storage, because `Knapsack` uses `i64` weights, values, and capacity. +][ + _Construction._ For a Subset Sum instance with binary choice variables $x_0, dots, x_(n-1)$, create a Knapsack instance with the same binary variables and define + $ w_i = a_i, quad v_i = a_i quad (0 <= i < n), quad C = B. $ + No auxiliary items or constraints are added: the target has exactly $n$ items. + + _Correctness._ ($arrow.r.double$) If a subset $A' subset.eq A$ satisfies $sum_(a_i in A') a_i = B$, select exactly those corresponding knapsack items. The total weight is $B$, so the choice is feasible, and because $v_i = w_i$ for every item, its total value is also $B$. Any feasible knapsack solution satisfies + $ sum_i v_i x_i = sum_i w_i x_i <= C = B, $ + so this feasible solution already attains the maximum possible value $B$. ($arrow.l.double$) If the Knapsack optimum has value $B$, then the optimal selection satisfies + $ B = sum_i v_i x_i = sum_i w_i x_i <= C = B, $ + hence the selected items have total weight exactly $B$, giving a valid Subset Sum witness. If no subset sums to $B$, then every feasible knapsack solution has value strictly less than $B$, so extracting any optimum does _not_ satisfy the source instance. + + _Variable mapping._ The binary variables are reused unchanged: $x_i = 1$ means “choose element $a_i$” in Subset Sum and “take item $i$” in Knapsack. + + _Solution extraction._ Return the target selection vector unchanged. +] + #let ks_qubo = load-example("Knapsack", "QUBO") #let ks_qubo_sol = ks_qubo.solutions.at(0) #let ks_qubo_num_items = ks_qubo.source.instance.weights.len() From 594ee63ed95fb167e3d7c5369a1249d7983a7778 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 16:04:12 +0800 Subject: [PATCH 4/4] chore: remove plan file after implementation --- .../plans/2026-03-20-subsetsum-to-knapsack.md | 263 ------------------ 1 file changed, 263 deletions(-) delete mode 100644 docs/plans/2026-03-20-subsetsum-to-knapsack.md diff --git a/docs/plans/2026-03-20-subsetsum-to-knapsack.md b/docs/plans/2026-03-20-subsetsum-to-knapsack.md deleted file mode 100644 index 71c6e703..00000000 --- a/docs/plans/2026-03-20-subsetsum-to-knapsack.md +++ /dev/null @@ -1,263 +0,0 @@ -# SubsetSum To Knapsack Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Add the `SubsetSum -> Knapsack` reduction rule, register a canonical example, and document the rule in the paper so the reduction graph exposes the path `KSatisfiability -> SubsetSum -> Knapsack -> QUBO`. - -**Architecture:** This reduction is a sat-to-opt embedding against the existing optimization `Knapsack` model, not a decision-threshold variant. The implementation maps each subset-sum element to a knapsack item with identical weight and value, uses the subset target as capacity, stores no extra mapping state beyond the target instance, and relies on source-side evaluation after solution extraction to distinguish SAT from UNSAT cases. Because `SubsetSum` uses `BigUint` and `Knapsack` uses `i64`, the reduction must perform checked conversion and fail loudly when the source instance cannot be represented in the target model. - -**Tech Stack:** Rust, cargo test, example-db exports, Typst paper, GitHub PR pipeline helpers. - ---- - -## Batch 1: Rule, Tests, and Canonical Example - -### Task 1: Add the failing rule tests first - -**Files:** -- Create: `src/unit_tests/rules/subsetsum_knapsack.rs` -- Read for reference: `src/unit_tests/rules/sat_maximumindependentset.rs` -- Read for helper APIs: `src/rules/test_helpers.rs` - -**Step 1: Write the failing tests** - -Add focused tests that define the intended behavior before implementing the rule: -- `test_subsetsum_to_knapsack_closed_loop`: - - Build `SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32)`. - - Reduce to `Knapsack`. - - Assert `assert_satisfaction_round_trip_from_optimization_target(&source, &reduction, "SubsetSum->Knapsack closed loop")`. -- `test_subsetsum_to_knapsack_target_structure`: - - Assert `weights == [3, 7, 1, 8, 4, 12, 5]`. - - Assert `values == [3, 7, 1, 8, 4, 12, 5]`. - - Assert `capacity == 15`. - - Assert `num_items == source.num_elements()`. -- `test_subsetsum_to_knapsack_unsat_extracts_non_solution`: - - Build an UNSAT source such as `SubsetSum::new(vec![2u32, 4, 6], 5u32)`. - - Reduce and solve the target with `solve_optimization_problem`. - - Extract the source config and assert `!source.evaluate(&extracted)`. -- `test_subsetsum_to_knapsack_panics_on_i64_overflow`: - - Build a source with one size larger than `i64::MAX` using `BigUint`. - - Assert the reduction panics with a message that mentions conversion to `i64`. -- `#[cfg(feature = "example-db")] test_subsetsum_to_knapsack_canonical_example_spec`: - - Find the rule example spec by id. - - Assert the exported example names are `SubsetSum` and `Knapsack`. - - Assert the example contains at least one witness. - -**Step 2: Run the focused test file to verify RED** - -Run: -```bash -cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture -``` - -Expected: -- Compile or test failure because the rule module and registrations do not exist yet. - -**Step 3: Commit note** - -Do not commit in RED state. Move directly to implementation after confirming the failure is for the missing rule. - -### Task 2: Implement the reduction rule and registration - -**Files:** -- Create: `src/rules/subsetsum_knapsack.rs` -- Modify: `src/rules/mod.rs` -- Read for reference: `src/rules/sat_maximumindependentset.rs` -- Read for model semantics: `src/models/misc/subset_sum.rs` -- Read for model semantics: `src/models/misc/knapsack.rs` - -**Step 1: Write the minimal production code** - -Implement `src/rules/subsetsum_knapsack.rs` with: -- `ReductionSubsetSumToKnapsack { target: Knapsack }` -- `ReductionResult` impl: - - `type Source = SubsetSum` - - `type Target = Knapsack` - - `target_problem()` returns `&self.target` - - `extract_solution()` returns `target_solution.to_vec()` -- `#[reduction(overhead = { num_items = "num_elements" })]` -- `impl ReduceTo for SubsetSum` - - Convert each source size to `i64` with `i64::try_from(...)` - - Use the converted vector for both target weights and target values - - Convert `target()` to `i64` for capacity using the same checked conversion - - Panic with a clear message if any conversion does not fit in `i64` -- `#[cfg(test)] #[path = "../unit_tests/rules/subsetsum_knapsack.rs"] mod tests;` - -Update `src/rules/mod.rs` to register `pub(crate) mod subsetsum_knapsack;` and extend `canonical_rule_example_specs()` with `subsetsum_knapsack::canonical_rule_example_specs()`. - -**Step 2: Run the focused tests to verify GREEN** - -Run: -```bash -cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture -``` - -Expected: -- The new rule tests pass. -- Any failures should now be behavioral, not missing-module errors. - -**Step 3: Refactor if needed** - -Keep the implementation minimal. Only extract a local helper if the conversion logic is duplicated enough to obscure the rule. - -### Task 3: Add the canonical example in the rule module - -**Files:** -- Modify: `src/rules/subsetsum_knapsack.rs` -- Read for reference: `src/rules/knapsack_qubo.rs` -- Read for example helpers: `src/example_db/specs.rs` - -**Step 1: Implement the canonical example builder** - -Inside `#[cfg(feature = "example-db")] pub(crate) fn canonical_rule_example_specs() -> Vec`: -- Reuse the issue’s 7-element canonical instance: - - Source: `SubsetSum::new(vec![3u32, 7, 1, 8, 4, 12, 5], 15u32)` - - Canonical witness: `source_config = target_config = vec![1, 0, 0, 0, 0, 1, 0]` -- Export it with `rule_example_with_witness::<_, Knapsack>(...)` -- Use id `subsetsum_to_knapsack` - -The witness must be valid in both directions, but it does not need to enumerate all optimal or satisfying assignments. - -**Step 2: Re-run the focused tests** - -Run: -```bash -cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --nocapture -``` - -Expected: -- The canonical example test now passes as well. - -### Task 4: Verify the rule and exports before the paper batch - -**Files:** -- Modify if generated and tracked: exported JSON/SVG files touched by the commands below - -**Step 1: Run broader verification for the new rule** - -Run: -```bash -cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --include-ignored -cargo run --features "example-db" --example export_examples -cargo run --example export_graph -cargo run --example export_schemas -``` - -Expected: -- Tests pass. -- Export commands succeed. -- Generated fixtures now contain the new rule example data needed by the paper. - -**Step 2: Inspect the exported example data** - -Check that the exported rule example contains: -- Source problem `SubsetSum` -- Target problem `Knapsack` -- The canonical witness pair with identical source/target configs - -**Step 3: Commit Batch 1** - -Run: -```bash -git add src/rules/subsetsum_knapsack.rs src/rules/mod.rs src/unit_tests/rules/subsetsum_knapsack.rs src/example_db/fixtures/examples.json docs/src/reduction_graph.json docs/src/problem_schemas.json -git commit -m "Implement #138: add SubsetSum to Knapsack reduction" -``` - -If the export paths differ from the expected tracked files, inspect `git status --short` and stage the actual tracked outputs instead of inventing paths. - -## Batch 2: Paper Entry and Final Verification - -### Task 5: Document the rule in the paper - -**Files:** -- Modify: `docs/paper/reductions.typ` -- Read for reference: nearby `SubsetSum` and `Knapsack` problem definitions -- Read for reference: `reduction-rule("KSatisfiability", "SubsetSum", ...)` -- Read for reference: `reduction-rule("Knapsack", "QUBO", ...)` - -**Step 1: Add the reduction-rule entry** - -Insert a new `#let` block and `#reduction-rule("SubsetSum", "Knapsack", ...)` between the existing `KSatisfiability -> SubsetSum` and `Knapsack -> QUBO` sections. - -The paper entry must reflect the implemented semantics: -- State that the reduction maps each element size to both item weight and item value, with capacity equal to the subset target. -- Explain the sat-to-opt correctness condition: SubsetSum is satisfiable iff the target Knapsack optimum reaches value `T`. -- Mention the checked `BigUint -> i64` representation boundary in prose or the worked example if needed, but do not claim the target supports arbitrary precision. -- Use exported example data rather than hardcoded duplicate constants where practical. -- Make the witness semantics explicit: the fixture stores one canonical witness, while multiple satisfying subsets may exist. - -**Step 2: Add a worked example** - -Use the exported 7-element canonical instance and walk through: -- Source set and target value -- Target weights, values, and capacity -- Why the chosen witness reaches value 15 exactly -- Why equality of weights and values makes extracted solutions identical - -**Step 3: Build the paper** - -Run: -```bash -make paper -``` - -Expected: -- Typst compiles successfully. -- No completeness warnings for an undocumented `SubsetSum -> Knapsack` edge remain. - -### Task 6: Final verification, cleanup, and push-ready state - -**Files:** -- Delete after execution: `docs/plans/2026-03-20-subsetsum-to-knapsack.md` - -**Step 1: Run final verification** - -Run: -```bash -make fmt -cargo test --features "ilp-highs example-db" subsetsum_to_knapsack -- --include-ignored -make clippy -git status --short -``` - -Expected: -- Formatting clean -- Rule-specific tests pass -- Clippy passes -- Only intended tracked files are modified - -**Step 2: Commit paper and generated artifacts** - -Run: -```bash -git add docs/paper/reductions.typ -git add -A -git commit -m "Document #138: add SubsetSum to Knapsack paper entry" -``` - -**Step 3: Remove the plan file after implementation** - -Run: -```bash -git rm docs/plans/2026-03-20-subsetsum-to-knapsack.md -git commit -m "chore: remove plan file after implementation" -``` - -**Step 4: Pre-push summary** - -Before push, prepare the PR implementation summary comment with: -- The new rule module and test coverage -- The canonical example and exported fixture updates -- The paper entry -- Any deviation from the original issue text: - - used optimization Knapsack semantics - - added checked `BigUint -> i64` conversion boundary - -**Step 5: Push** - -Run: -```bash -git push -``` - -Stop if verification reveals unrelated changes or failures. Fix them before pushing.