|
| 1 | +--- |
| 2 | +name: add-rule |
| 3 | +description: Use when adding a new reduction rule to the codebase, either from an issue or interactively |
| 4 | +--- |
| 5 | + |
| 6 | +# Add Rule |
| 7 | + |
| 8 | +Step-by-step guide for adding a new reduction rule (A -> B) to the codebase. |
| 9 | + |
| 10 | +## Step 0: Gather Required Information |
| 11 | + |
| 12 | +Before any implementation, collect all required information. If called from `issue-to-pr`, the issue should already provide these. If used standalone, brainstorm with the user to fill in every item below. |
| 13 | + |
| 14 | +### Required Information Checklist |
| 15 | + |
| 16 | +| # | Item | Description | Example | |
| 17 | +|---|------|-------------|---------| |
| 18 | +| 1 | **Source problem** | The problem being reduced FROM (must already exist) | `MinimumVertexCover<SimpleGraph, i32>` | |
| 19 | +| 2 | **Target problem** | The problem being reduced TO (must already exist) | `MaximumIndependentSet<SimpleGraph, i32>` | |
| 20 | +| 3 | **Reduction algorithm** | How to transform source instance to target | "Copy graph and weights; IS on same graph as VC" | |
| 21 | +| 4 | **Solution extraction** | How to map target solution back to source | "Complement: `1 - x` for each variable" | |
| 22 | +| 5 | **Correctness argument** | Why the reduction preserves optimality | "S is independent set iff V\S is vertex cover" | |
| 23 | +| 6 | **Size overhead** | How target size relates to source size | `num_vertices: poly!(num_vertices), num_edges: poly!(num_edges)` | |
| 24 | +| 7 | **Concrete example** | A small worked-out instance (tutorial style, clear intuition) | "Triangle graph: VC={0,1} -> IS={2}" | |
| 25 | +| 8 | **Solving strategy** | How to solve the target problem | "BruteForce, or existing ILP reduction" | |
| 26 | +| 9 | **Reference** | Paper, textbook, or URL for the reduction | URL or citation | |
| 27 | + |
| 28 | +If any item is missing, ask the user to provide it. Put a high standard on item 7 (concrete example): it must be in tutorial style with clear intuition and easy to understand. Do NOT proceed until the checklist is complete. |
| 29 | + |
| 30 | +## Reference Implementations |
| 31 | + |
| 32 | +Read these first to understand the patterns: |
| 33 | +- **Reduction rule:** `src/rules/minimumvertexcover_maximumindependentset.rs` |
| 34 | +- **Reduction tests:** `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs` |
| 35 | +- **Example program:** `examples/reduction_minimumvertexcover_to_maximumindependentset.rs` |
| 36 | +- **Example registration:** `tests/suites/examples.rs` |
| 37 | +- **Paper entry:** search `docs/paper/reductions.typ` for `MinimumVertexCover` `MaximumIndependentSet` |
| 38 | +- **Traits:** `src/rules/traits.rs` (`ReduceTo<T>`, `ReductionResult`) |
| 39 | + |
| 40 | +## Step 1: Implement the reduction |
| 41 | + |
| 42 | +Create `src/rules/<source>_<target>.rs` (all lowercase, no underscores between words within a problem name): |
| 43 | + |
| 44 | +```rust |
| 45 | +// Required structure: |
| 46 | +// 1. ReductionResult struct (holds the target problem + mapping state) |
| 47 | +// 2. ReductionResult trait impl (target_problem + extract_solution) |
| 48 | +// 3. #[reduction(overhead = { ... })] on ReduceTo impl |
| 49 | +// 4. ReduceTo trait impl (reduce_to method) |
| 50 | +// 5. #[cfg(test)] #[path = "..."] mod tests; |
| 51 | +``` |
| 52 | + |
| 53 | +Key elements: |
| 54 | + |
| 55 | +**ReductionResult struct:** |
| 56 | +```rust |
| 57 | +#[derive(Debug, Clone)] |
| 58 | +pub struct ReductionXToY { |
| 59 | + target: TargetType, |
| 60 | + // any additional mapping state needed for extract_solution |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +**ReductionResult trait impl:** |
| 65 | +```rust |
| 66 | +impl ReductionResult for ReductionXToY { |
| 67 | + type Source = SourceType; |
| 68 | + type Target = TargetType; |
| 69 | + fn target_problem(&self) -> &Self::Target { &self.target } |
| 70 | + fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> { |
| 71 | + // Map target solution back to source solution |
| 72 | + } |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +**ReduceTo with `#[reduction]` macro:** |
| 77 | +```rust |
| 78 | +#[reduction( |
| 79 | + overhead = { |
| 80 | + ReductionOverhead::new(vec![ |
| 81 | + ("field_name", poly!(source_field)), |
| 82 | + ]) |
| 83 | + } |
| 84 | +)] |
| 85 | +impl ReduceTo<TargetType> for SourceType { |
| 86 | + type Result = ReductionXToY; |
| 87 | + fn reduce_to(&self) -> Self::Result { ... } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +## Step 2: Register in mod.rs |
| 92 | + |
| 93 | +Add to `src/rules/mod.rs`: |
| 94 | +- `mod <source>_<target>;` |
| 95 | +- If feature-gated (e.g., ILP): wrap with `#[cfg(feature = "ilp-solver")]` |
| 96 | + |
| 97 | +## Step 3: Write unit tests |
| 98 | + |
| 99 | +Create `src/unit_tests/rules/<source>_<target>.rs`: |
| 100 | + |
| 101 | +**Required: closed-loop test** (`test_<source>_to_<target>_closed_loop`): |
| 102 | +```rust |
| 103 | +// 1. Create source problem instance |
| 104 | +// 2. Reduce: let reduction = ReduceTo::<Target>::reduce_to(&source); |
| 105 | +// 3. Solve target: solver.find_all_best(reduction.target_problem()) |
| 106 | +// 4. Extract: reduction.extract_solution(&target_sol) |
| 107 | +// 5. Verify: extracted solution is valid and optimal for source |
| 108 | +``` |
| 109 | + |
| 110 | +Additional recommended tests: |
| 111 | +- Verify target problem structure (correct size, edges, constraints) |
| 112 | +- Edge cases (empty graph, single vertex, etc.) |
| 113 | +- Weight preservation (if applicable) |
| 114 | + |
| 115 | +Link via `#[cfg(test)] #[path = "..."] mod tests;` at the bottom of the rule file. |
| 116 | + |
| 117 | +## Step 4: Write example program |
| 118 | + |
| 119 | +Create `examples/reduction_<source>_to_<target>.rs`: |
| 120 | + |
| 121 | +Required structure: |
| 122 | +- `pub fn run()` -- main logic (required for test harness) |
| 123 | +- `fn main() { run() }` -- entry point |
| 124 | +- Use regular comments (`//`), not doc comments |
| 125 | +- Create source instance, reduce, solve, extract, verify, export JSON |
| 126 | + |
| 127 | +Register in `tests/suites/examples.rs`: |
| 128 | +```rust |
| 129 | +example_test!(reduction_<source>_to_<target>); |
| 130 | +// ... |
| 131 | +example_fn!(test_<source>_to_<target>, reduction_<source>_to_<target>); |
| 132 | +``` |
| 133 | + |
| 134 | +## Step 5: Document in paper |
| 135 | + |
| 136 | +Update `docs/paper/reductions.typ`: |
| 137 | + |
| 138 | +```typst |
| 139 | +#reduction-rule("Source", "Target", |
| 140 | + example: true, |
| 141 | + example-caption: [Caption text], |
| 142 | +)[ |
| 143 | + Reduction rule statement... |
| 144 | +][ |
| 145 | + Proof sketch... |
| 146 | +] |
| 147 | +``` |
| 148 | + |
| 149 | +Present the example in tutorial style with clear intuition. Reference the KColoring -> QUBO section for style guidance. |
| 150 | + |
| 151 | +## Step 6: Regenerate graph and verify |
| 152 | + |
| 153 | +```bash |
| 154 | +cargo run --example export_graph # Update reduction_graph.json |
| 155 | +make test clippy # Must pass |
| 156 | +``` |
| 157 | + |
| 158 | +## Solver Rules |
| 159 | + |
| 160 | +- If the target problem already has a solver, use it directly. |
| 161 | +- If the solving strategy requires ILP, implement the ILP reduction rule alongside (feature-gated under `ilp-solver`). |
| 162 | +- If a custom solver is needed, implement in `src/solvers/` and document. |
| 163 | + |
| 164 | +## CLI Impact |
| 165 | + |
| 166 | +Adding a reduction rule does NOT require CLI changes -- the reduction graph is auto-generated from `#[reduction]` macros and the CLI discovers paths dynamically. However, both source and target models must already be registered in the CLI dispatch table (see `add-model` skill). |
| 167 | + |
| 168 | +## File Naming |
| 169 | + |
| 170 | +- Rule file: `src/rules/<sourcelower>_<targetlower>.rs` -- no underscores within a problem name |
| 171 | + - e.g., `maximumindependentset_qubo.rs`, `minimumvertexcover_maximumindependentset.rs` |
| 172 | +- Example file: `examples/reduction_<source>_to_<target>.rs` |
| 173 | + - e.g., `reduction_minimumvertexcover_to_maximumindependentset.rs` |
| 174 | +- Test file: `src/unit_tests/rules/<sourcelower>_<targetlower>.rs` |
| 175 | + |
| 176 | +## Common Mistakes |
| 177 | + |
| 178 | +| Mistake | Fix | |
| 179 | +|---------|-----| |
| 180 | +| Forgetting `#[reduction(...)]` macro | Required for compile-time registration in the reduction graph | |
| 181 | +| Wrong overhead polynomial | Must accurately reflect the size relationship | |
| 182 | +| Missing `extract_solution` mapping state | Store any index maps needed in the ReductionResult struct | |
| 183 | +| Example missing `pub fn run()` | Required for the test harness (`include!` pattern) | |
| 184 | +| Not registering example in `tests/suites/examples.rs` | Must add both `example_test!` and `example_fn!` | |
| 185 | +| Not regenerating reduction graph | Run `cargo run --example export_graph` after adding a rule | |
| 186 | +| Source/target model not in CLI dispatch | Both problems must be registered -- use `add-model` skill first | |
0 commit comments