Skip to content

Commit 7bbb796

Browse files
GiggleLiuclaude
andcommitted
docs: complete architecture documentation
Reorganize arch.md with layered structure (Concepts + Implementation) for each section: - Problems: trait design, type parameters, variants - Rules: reduction workflow, ReduceTo/ReductionResult, #[reduction] macro - Registry: variant IDs, reduction graph, compile-time metadata - Solvers: trait interface, BruteForce and ILPSolver - File I/O: JSON serialization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent bbdc7a8 commit 7bbb796

1 file changed

Lines changed: 277 additions & 0 deletions

File tree

docs/src/arch.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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

Comments
 (0)