|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Problems |
| 4 | + |
| 5 | +Every computational problem implements the `Problem` trait. A problem defines: |
| 6 | + |
| 7 | +- **Variables** — the unknowns to be solved (e.g., vertex assignments, boolean values) |
| 8 | +- **Flavors** — possible values each variable can take (usually 2 for binary problems) |
| 9 | +- **Solution size** — the objective value for a given configuration |
| 10 | + |
| 11 | +Each problem type has its own parameters. For example: |
| 12 | + |
| 13 | +- `MaximumIndependentSet<G, W>` — parameterized by graph type `G` and weight type `W` |
| 14 | +- `Satisfiability<W>` — CNF formula with optional clause weights |
| 15 | +- `QUBO<W>` — parameterized by weight type only |
| 16 | + |
| 17 | +Graph-based problems support multiple topologies: |
| 18 | + |
| 19 | +| Graph Type | Description | |
| 20 | +|------------|-------------| |
| 21 | +| `SimpleGraph` | Standard adjacency-based graph | |
| 22 | +| `GridGraph` | Vertices on a regular grid | |
| 23 | +| `UnitDiskGraph` | Edges connect vertices within a distance threshold | |
| 24 | +| `HyperGraph` | Edges connecting any number of vertices | |
| 25 | + |
| 26 | +Problem variants appear as separate nodes in the reduction graph when they have distinct reductions: |
| 27 | + |
| 28 | +``` |
| 29 | +MaximumIndependentSet # base variant |
| 30 | +MaximumIndependentSet/GridGraph # different graph topology |
| 31 | +MaximumIndependentSet/Weighted # weighted objective |
| 32 | +``` |
| 33 | + |
| 34 | +Evaluating a configuration returns both validity and objective value: |
| 35 | + |
| 36 | +```rust |
| 37 | +let config = vec![1, 0, 1, 0]; // Variable assignments |
| 38 | +let result = problem.solution_size(&config); |
| 39 | +// result.is_valid: bool |
| 40 | +// result.size: objective value |
| 41 | +``` |
| 42 | + |
| 43 | +### Implementation |
| 44 | + |
| 45 | +Implement the `Problem` trait. Key methods: |
| 46 | + |
| 47 | +| Method | Purpose | |
| 48 | +|--------|---------| |
| 49 | +| `NAME` | Problem identifier (e.g., `"MaximumIndependentSet"`) | |
| 50 | +| `variant()` | Key-value pairs identifying this variant | |
| 51 | +| `num_variables()` | Number of unknowns | |
| 52 | +| `num_flavors()` | Values per variable (usually 2) | |
| 53 | +| `solution_size()` | Evaluate a configuration | |
| 54 | + |
| 55 | +For problems with explicit constraints, also implement `ConstraintSatisfactionProblem`. |
| 56 | + |
| 57 | +See [Adding Models](claude.md) for the full guide. |
| 58 | + |
| 59 | +## Rules |
| 60 | + |
| 61 | +A **reduction** transforms one problem into another while preserving solutions. Given a source problem A and target problem B: |
| 62 | + |
| 63 | +1. **Reduce** — convert A to B |
| 64 | +2. **Solve** — find solution to B |
| 65 | +3. **Extract** — map B's solution back to A |
| 66 | + |
| 67 | +```rust |
| 68 | +// Reduce: MaximumIndependentSet → QUBO |
| 69 | +let reduction = problem.reduce_to::<QUBO<f64>>(); |
| 70 | +let qubo = reduction.target_problem(); |
| 71 | + |
| 72 | +// Solve the target |
| 73 | +let qubo_solution = solver.find_best(qubo); |
| 74 | + |
| 75 | +// Extract back to source |
| 76 | +let original_solution = reduction.extract_solution(&qubo_solution[0]); |
| 77 | +``` |
| 78 | + |
| 79 | +Reductions track size overhead for complexity analysis: |
| 80 | + |
| 81 | +```rust |
| 82 | +let source_size = reduction.source_size(); // ProblemSize |
| 83 | +let target_size = reduction.target_size(); // ProblemSize |
| 84 | +``` |
| 85 | + |
| 86 | +The reduction graph shows all available transformations: |
| 87 | + |
| 88 | +``` |
| 89 | +Satisfiability ──→ MaximumIndependentSet ──→ QUBO |
| 90 | + │ |
| 91 | + ▼ |
| 92 | + MinimumVertexCover |
| 93 | +``` |
| 94 | + |
| 95 | +Not all reductions preserve optimality — some only preserve satisfiability. The graph encodes this metadata. |
| 96 | + |
| 97 | +### Implementation |
| 98 | + |
| 99 | +A reduction requires two pieces: |
| 100 | + |
| 101 | +1. **Result struct** — holds the target problem and extraction logic |
| 102 | +2. **`ReduceTo<T>` impl** — performs the reduction |
| 103 | + |
| 104 | +```rust |
| 105 | +#[derive(Clone)] |
| 106 | +pub struct ReductionAToB { |
| 107 | + target: B, |
| 108 | + // ... mapping data for extraction |
| 109 | +} |
| 110 | + |
| 111 | +impl ReductionResult for ReductionAToB { |
| 112 | + type Source = A; |
| 113 | + type Target = B; |
| 114 | + |
| 115 | + fn target_problem(&self) -> &B { &self.target } |
| 116 | + fn extract_solution(&self, target_sol: &[usize]) -> Vec<usize> { /* ... */ } |
| 117 | + fn source_size(&self) -> ProblemSize { /* ... */ } |
| 118 | + fn target_size(&self) -> ProblemSize { /* ... */ } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +Use the `#[reduction]` macro to register in the global inventory: |
| 123 | + |
| 124 | +```rust |
| 125 | +#[reduction(A -> B)] |
| 126 | +impl ReduceTo<B> for A { |
| 127 | + type Result = ReductionAToB; |
| 128 | + |
| 129 | + fn reduce_to(&self) -> Self::Result { /* ... */ } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +The macro generates `inventory::submit!` calls, making the reduction discoverable at compile time for the reduction graph. |
| 134 | + |
| 135 | +See [Adding Reductions](claude.md) for the full guide. |
| 136 | + |
| 137 | +## Registry |
| 138 | + |
| 139 | +The **reduction graph** is a directed graph where: |
| 140 | + |
| 141 | +- **Nodes** — problem variants (e.g., `MaximumIndependentSet/GridGraph`) |
| 142 | +- **Edges** — available reductions between variants |
| 143 | + |
| 144 | +Variant IDs follow the pattern `ProblemName[/GraphType][/Weighted]`: |
| 145 | + |
| 146 | +| Variant ID | Meaning | |
| 147 | +|------------|---------| |
| 148 | +| `MaximumIndependentSet` | Base variant (SimpleGraph, unweighted) | |
| 149 | +| `MaximumIndependentSet/GridGraph` | GridGraph topology | |
| 150 | +| `MaximumIndependentSet/Weighted` | Weighted objective | |
| 151 | +| `MaximumIndependentSet/GridGraph/Weighted` | Both | |
| 152 | + |
| 153 | +The graph is exported to `reduction_graph.json` for visualization and documentation: |
| 154 | + |
| 155 | +```json |
| 156 | +{ |
| 157 | + "nodes": [ |
| 158 | + {"name": "Satisfiability", "variant": {}, "category": "satisfiability", "doc_path": "..."}, |
| 159 | + {"name": "MaximumIndependentSet", "variant": {"graph": "GridGraph"}, "category": "graph", "doc_path": "..."} |
| 160 | + ], |
| 161 | + "edges": [ |
| 162 | + {"source": {"name": "Satisfiability", "variant": {}}, "target": {"name": "MaximumIndependentSet", "variant": {}}} |
| 163 | + ] |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +Problem schemas (`problem_schemas.json`) describe each problem's structure: |
| 168 | + |
| 169 | +```json |
| 170 | +[ |
| 171 | + { |
| 172 | + "name": "Satisfiability", |
| 173 | + "category": "satisfiability", |
| 174 | + "description": "Find satisfying assignment for CNF formula", |
| 175 | + "fields": [ |
| 176 | + {"name": "num_vars", "type_name": "usize", "description": "Number of Boolean variables"}, |
| 177 | + {"name": "clauses", "type_name": "Vec<CNFClause>", "description": "Clauses in conjunctive normal form"}, |
| 178 | + {"name": "weights", "type_name": "Vec<W>", "description": "Clause weights for MAX-SAT"} |
| 179 | + ] |
| 180 | + } |
| 181 | +] |
| 182 | +``` |
| 183 | + |
| 184 | +Use the interactive diagram in the [mdBook documentation](https://codingthrust.github.io/problem-reductions/) to explore available reductions. |
| 185 | + |
| 186 | +### Implementation |
| 187 | + |
| 188 | +Reductions are collected at compile time using the `inventory` crate. The `#[reduction]` macro registers metadata: |
| 189 | + |
| 190 | +```rust |
| 191 | +#[reduction(A -> B)] |
| 192 | +impl ReduceTo<B> for A { /* ... */ } |
| 193 | + |
| 194 | +// Expands to include: |
| 195 | +// inventory::submit! { ReductionMeta { source: "A", target: "B", ... } } |
| 196 | +``` |
| 197 | + |
| 198 | +To regenerate the exports after adding rules or problems: |
| 199 | + |
| 200 | +```bash |
| 201 | +cargo run --example export_graph # writes docs/paper/reduction_graph.json |
| 202 | +cargo run --example export_schemas # writes docs/paper/problem_schemas.json |
| 203 | +``` |
| 204 | + |
| 205 | +## Solvers |
| 206 | + |
| 207 | +Solvers find optimal solutions to problems. The library provides: |
| 208 | + |
| 209 | +| Solver | Description | Use case | |
| 210 | +|--------|-------------|----------| |
| 211 | +| `BruteForce` | Enumerates all configurations | Small instances (< 20 variables) | |
| 212 | +| `ILPSolver` | Integer Linear Programming (HiGHS) | Larger instances, requires `ilp` feature | |
| 213 | + |
| 214 | +All solvers implement the `Solver` trait: |
| 215 | + |
| 216 | +```rust |
| 217 | +let solver = BruteForce::new(); |
| 218 | +let solutions = solver.find_best(&problem); // Best solution(s) |
| 219 | +let with_size = solver.find_best_with_size(&problem); // With objective values |
| 220 | +``` |
| 221 | + |
| 222 | +Solvers work with reductions — solve the target problem, then extract: |
| 223 | + |
| 224 | +```rust |
| 225 | +let reduction = problem.reduce_to::<QUBO<f64>>(); |
| 226 | +let qubo_solutions = solver.find_best(reduction.target_problem()); |
| 227 | +let original = reduction.extract_solution(&qubo_solutions[0]); |
| 228 | +``` |
| 229 | + |
| 230 | +### Implementation |
| 231 | + |
| 232 | +The `Solver` trait: |
| 233 | + |
| 234 | +```rust |
| 235 | +pub trait Solver { |
| 236 | + fn find_best<P: Problem>(&self, problem: &P) -> Vec<Vec<usize>>; |
| 237 | + fn find_best_with_size<P: Problem>(&self, problem: &P) |
| 238 | + -> Vec<(Vec<usize>, SolutionSize<P::Size>)>; |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +`ILPSolver` additionally provides `solve_reduced()` for problems implementing `ReduceTo<ILP>`. |
| 243 | + |
| 244 | +Enable with: |
| 245 | + |
| 246 | +```toml |
| 247 | +[dependencies] |
| 248 | +problemreductions = { version = "0.1", features = ["ilp"] } |
| 249 | +``` |
| 250 | + |
| 251 | +## File I/O |
| 252 | + |
| 253 | +All problem types support JSON serialization for persistence and interoperability. |
| 254 | + |
| 255 | +```rust |
| 256 | +use problemreductions::io::{write_problem, read_problem, FileFormat}; |
| 257 | + |
| 258 | +// Write |
| 259 | +write_problem(&problem, "problem.json", FileFormat::Json)?; |
| 260 | + |
| 261 | +// Read |
| 262 | +let problem: MaximumIndependentSet<i32> = read_problem("problem.json", FileFormat::Json)?; |
| 263 | +``` |
| 264 | + |
| 265 | +String serialization: |
| 266 | + |
| 267 | +```rust |
| 268 | +use problemreductions::io::{to_json, from_json}; |
| 269 | + |
| 270 | +let json = to_json(&problem)?; |
| 271 | +let restored: MaximumIndependentSet<i32> = from_json(&json)?; |
| 272 | +``` |
| 273 | + |
| 274 | +| Format | Description | |
| 275 | +|--------|-------------| |
| 276 | +| `Json` | Pretty-printed | |
| 277 | +| `JsonCompact` | No whitespace | |
0 commit comments