Skip to content

Commit 0ea5572

Browse files
GiggleLiuclaude
andauthored
fix: use directed edges instead of bidirectional in reduction graph (#42)
* fix: use directed edges instead of bidirectional in reduction graph The JSON export used a single edge with `bidirectional: true` for reversible reductions, but the source/target direction depended on non-deterministic inventory iteration order. This broke the Typst paper's label resolution in CI (Deploy Documentation workflow). Replace with two separate directed edges per reversible reduction. This makes the JSON deterministic and the paper's link resolution trivial. Add reduction-rule entries for the 5 newly-explicit reverse directions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add negative assertion for removed bidirectional field Address Copilot review comment: assert that the legacy "bidirectional" field is absent in test_to_json_string to catch accidental regressions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update .claude/rules to match actual codebase APIs Full audit of all rule files against source code: - adding-models.md: Fix Problem trait template (was missing NAME, variant(), Size, num_flavors(), energy_mode(), solution_size(); had non-existent is_valid_solution(); wrong import path for ProblemSize). Add specialized category. - testing.md: Fix closed-loop test pattern to use actual API (ReduceTo::<T>::reduce_to, solution_size().is_valid). Remove stale cargo-tarpaulin reference (project uses cargo-llvm-cov). - documentation.md: Fix problem-def signature (name, body not name, title). Fix reduction-rule params (example is bool, no overhead param). Note JSON-based examples, not Rust code. - adding-reductions.md: Fix make export-graph -> cargo run. - CLAUDE.md: Fix problem-def signature, add directed reduction note. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: simplify .claude/rules by pointing to reference implementations Replace verbose pseudo-code templates with references to actual files (kcoloring.rs, VC↔IS reduction, etc.) so rules stay in sync with code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c400651 commit 0ea5572

11 files changed

Lines changed: 413 additions & 310 deletions

File tree

.claude/CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ Reduction graph nodes use variant IDs: `ProblemName[/GraphType][/Weighted]`
9797
- Test naming: `test_<source>_to_<target>_closed_loop`
9898

9999
### Paper (docs/paper/reductions.typ)
100-
- `problem-def(name, title, body)` — defines a problem with auto-generated schema, reductions list, and label `<def:ProblemName>`
101-
- `reduction-rule(source, target, ...)` — generates a theorem with label `<thm:Source-to-Target>` and registers in `covered-rules` state
100+
- `problem-def(name)[body]` — defines a problem with auto-generated schema, reductions list, and label `<def:ProblemName>`. Title comes from `display-name` dict.
101+
- `reduction-rule(source, target, example: bool, ...)[rule][proof]` — generates a theorem with label `<thm:Source-to-Target>` and registers in `covered-rules` state. Overhead auto-derived from JSON edge data.
102+
- Every directed reduction needs its own `reduction-rule` entry
102103
- Completeness warnings auto-check that all JSON graph nodes/edges are covered in the paper
103104
- `display-name` dict maps `ProblemName` to display text
104105

.claude/rules/adding-models.md

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,23 @@ paths:
55

66
# Adding a Model (Problem Type)
77

8-
## 1. Define the Model
9-
Create `src/models/<category>/<name>.rs`:
10-
11-
```rust
12-
use serde::{Deserialize, Serialize};
13-
use crate::traits::{Problem, ProblemSize};
14-
15-
#[derive(Clone, Debug, Serialize, Deserialize)]
16-
pub struct MyProblem<W> {
17-
// Problem data fields
18-
pub size: usize,
19-
pub weights: Vec<W>,
20-
// ...
21-
}
22-
23-
impl<W: Clone> Problem for MyProblem<W> {
24-
fn num_variables(&self) -> usize { ... }
25-
fn problem_size(&self) -> ProblemSize { ... }
26-
fn is_valid_solution(&self, solution: &[usize]) -> bool { ... }
27-
}
28-
```
29-
30-
## 2. Register in Module
31-
Add to `src/models/<category>/mod.rs`:
32-
```rust
33-
mod my_problem;
34-
pub use my_problem::MyProblem;
35-
```
36-
37-
## 3. Categories
38-
Place models in appropriate category:
39-
- `src/models/satisfiability/` - Satisfiability, KSatisfiability, CircuitSAT
40-
- `src/models/graph/` - MaximumIndependentSet, MinimumVertexCover, KColoring, etc.
41-
- `src/models/set/` - MinimumSetCovering, MaximumSetPacking
42-
- `src/models/optimization/` - SpinGlass, QUBO, ILP
43-
44-
## 4. Required Traits
45-
- `Serialize`, `Deserialize` - JSON I/O support
46-
- `Clone`, `Debug` - Standard Rust traits
47-
- `Problem` - Core trait with `num_variables()`, `problem_size()`, `is_valid_solution()`
48-
- Consider `ConstraintSatisfactionProblem` if applicable
49-
50-
## 5. Naming
51-
Use explicit optimization prefixes: `Maximum` for maximization, `Minimum` for minimization (e.g., `MaximumIndependentSet`, `MinimumVertexCover`).
8+
**Reference implementation:** `src/models/graph/kcoloring.rs`
9+
10+
## Steps
11+
12+
1. **Create** `src/models/<category>/<name>.rs` — follow the reference for struct definition, `Problem` impl, and optionally `ConstraintSatisfactionProblem` impl.
13+
2. **Register** in `src/models/<category>/mod.rs`.
14+
3. **Add tests** in `src/unit_tests/models/<category>/<name>.rs` (linked via `#[path]`).
15+
4. **Document** in `docs/paper/reductions.typ`: add `display-name` entry and `#problem-def("Name")[definition...]`.
5216

53-
## 6. Documentation
54-
Document in `docs/paper/reductions.typ` using `#problem-def("ProblemName", "Display Title")[...]`
17+
## Categories
5518

56-
## Anti-patterns
57-
- Don't create models without JSON serialization support
58-
- Don't forget to implement `is_valid_solution()` correctly
59-
- Don't use concrete types when generic `W` is appropriate
19+
- `src/models/satisfiability/` — Satisfiability, KSatisfiability, CircuitSAT
20+
- `src/models/graph/` — MaximumIndependentSet, MinimumVertexCover, KColoring, etc.
21+
- `src/models/set/` — MinimumSetCovering, MaximumSetPacking
22+
- `src/models/optimization/` — SpinGlass, QUBO, ILP
23+
- `src/models/specialized/` — Factoring
24+
25+
## Naming
26+
27+
Use explicit optimization prefixes: `Maximum` for maximization, `Minimum` for minimization (e.g., `MaximumIndependentSet`, `MinimumVertexCover`).

.claude/rules/adding-reductions.md

Lines changed: 25 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,104 +3,45 @@ paths:
33
- "src/rules/**/*.rs"
44
---
55

6-
# Adding a Reduction Rule (A B)
6+
# Adding a Reduction Rule (A -> B)
77

8-
## 0. Brainstorm & Generate Test Data First
8+
**Reference implementation:** `src/rules/minimumvertexcover_maximumindependentset.rs`
9+
**Reference test:** `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs`
10+
**Reference example:** `examples/reduction_minimumvertexcover_to_maximumindependentset.rs`
11+
**Reference paper entry:** `docs/paper/reductions.typ` (search for `MinimumVertexCover` `MaximumIndependentSet`)
912

10-
Before writing any Rust code, follow this workflow:
13+
## 0. Before Writing Code
1114

12-
1. **Brainstorm the reduction** — use `superpowers:brainstorming` to discuss with the user:
13-
- Research the mathematical formulation (paper, textbook, or derive it)
14-
- Understand the variable mapping and constraint encoding
15-
- Discuss implementation approach: penalty values, matrix construction, solution extraction
16-
- Read reference implementations in the codebase (e.g., `src/rules/spinglass_qubo.rs`) to understand conventions
17-
- Agree on scope (weighted vs unweighted, specific graph types, const generics)
18-
2. **Generate ground truth test data** — use an existing library (e.g., Python with qubogen, qubovert, or networkx) to create small instances, reduce them, brute-force solve both sides, and export as JSON to `tests/data/<target>/`. It is recommended to download the relevant package and check the existing tests to understand how to construct tests. To generate the test data, you can use the following command:
19-
```bash
20-
# Example: generate QUBO test data
21-
cd scripts && uv run python generate_qubo_tests.py
22-
```
23-
3. **Create a practical example** — design a small, explainable instance for `examples/` (e.g., "wireless tower placement" for MaximumIndependentSet, "map coloring" for KColoring). This example will also appear in the `docs/paper/reductions.typ`.
24-
4. **Write the implementation plan** — save to `docs/plans/` using `superpowers:writing-plans`. The plan must include implementation details from the brainstorming session (formulas, penalty terms, matrix construction, variable indexing).
15+
1. **Brainstorm** — use `superpowers:brainstorming` to discuss with the user:
16+
- The math (variable mapping, constraint encoding, penalty terms)
17+
- Which example instance to use in `examples/` (must be small, human-explainable, and agreed with the user)
18+
2. **Generate ground truth** — use Python scripts in `scripts/` (run with `uv`) to create test data in `tests/data/<target>/`.
19+
3. **Write plan** — save to `docs/plans/` using `superpowers:writing-plans`.
2520

26-
## 1. Implementation
21+
## 1. Implement
2722

28-
Create `src/rules/<source>_<target>.rs` following the pattern in `src/rules/spinglass_qubo.rs`:
23+
Create `src/rules/<source>_<target>.rs` following the reference. Key pieces:
24+
- `ReductionResult` struct + impl (`target_problem`, `extract_solution`, `source_size`, `target_size`)
25+
- `#[reduction(...)]` macro on `ReduceTo<Target> for Source` impl (auto-generates `inventory::submit!`)
26+
- `#[cfg(test)] #[path = ...]` linking to unit tests
2927

30-
```rust
31-
use crate::reduction;
28+
Register in `src/rules/mod.rs`.
3229

33-
#[derive(Debug, Clone)]
34-
pub struct ReductionSourceToTarget {
35-
target: TargetProblem<...>,
36-
source_size: ProblemSize,
37-
// + any metadata needed for extract_solution
38-
}
30+
## 2. Test
3931

40-
impl ReductionResult for ReductionSourceToTarget {
41-
type Source = SourceProblem<...>;
42-
type Target = TargetProblem<...>;
43-
44-
fn target_problem(&self) -> &Self::Target { &self.target }
45-
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> { ... }
46-
fn source_size(&self) -> ProblemSize { self.source_size.clone() }
47-
fn target_size(&self) -> ProblemSize { self.target.problem_size() }
48-
}
49-
50-
#[reduction(
51-
overhead = { ReductionOverhead::new(vec![...]) }
52-
)]
53-
impl ReduceTo<TargetProblem<...>> for SourceProblem<...> {
54-
type Result = ReductionSourceToTarget;
55-
fn reduce_to(&self) -> Self::Result { ... }
56-
}
57-
58-
#[cfg(test)]
59-
#[path = "../unit_tests/rules/<source>_<target>.rs"]
60-
mod tests;
61-
```
62-
63-
The `#[reduction]` macro auto-generates the `inventory::submit!` call. Optional attributes: `source_graph`, `target_graph`, `source_weighted`, `target_weighted`.
64-
65-
Register module in `src/rules/mod.rs`:
66-
```rust
67-
mod source_target;
68-
pub use source_target::ReductionSourceToTarget;
69-
```
70-
71-
## 2. Tests (Required)
72-
73-
- **Unit tests** in `src/unit_tests/rules/<source>_<target>.rs` — closed-loop + edge cases. See `rules/testing.md`.
74-
- **Integration tests** in `tests/suites/reductions.rs` — compare against JSON ground truth from step 0.
75-
- Test name: `test_<source>_to_<target>_closed_loop`
32+
- **Unit tests** in `src/unit_tests/rules/<source>_<target>.rs` — closed-loop + edge cases (see reference test).
33+
- **Integration tests** in `tests/suites/reductions.rs` — compare against JSON ground truth.
7634

7735
## 3. Example Program
7836

79-
Add a round-trip demo to `examples/` showing a practical, explainable instance:
80-
1. Create source problem with a real-world story
81-
2. Reduce to target, solve, extract solution
82-
3. Print human-readable explanation
37+
Add `examples/reduction_<source>_to_<target>.rs` — create, reduce, solve, extract, verify, export JSON (see reference example).
8338

84-
## 4. Documentation
39+
## 4. Document
8540

86-
Update `docs/paper/reductions.typ` (see `rules/documentation.md` for the pattern):
87-
- Add `reduction-rule("Source", "Target", ...)` theorem with proof sketch
88-
- Add Rust code example from the example program
89-
- Add `display-name` entry if the problem is new
41+
Update `docs/paper/reductions.typ` — add `reduction-rule("Source", "Target", ...)` with proof sketch (see `rules/documentation.md`).
9042

91-
The goal is to 1. prove the correctness of the reduction to human beings. 2. provide a minimal working example to the readers.
43+
## 5. Regenerate Graph
9244

93-
Citations must be verifiable. Use `[Folklore]` or `` for trivial reductions.
94-
95-
## 5. Regenerate Reduction Graph
9645
```bash
97-
make export-graph
46+
cargo run --example export_graph
9847
```
99-
100-
## Anti-patterns
101-
- Don't write Rust code before understanding the math and having test data
102-
- Don't create reductions without closed-loop tests
103-
- Don't forget `inventory::submit!` registration (reduction graph won't update)
104-
- Don't hardcode weights - use generic `W` parameter
105-
- Don't skip overhead polynomial specification
106-
- Don't skip the example program — every reduction needs an explainable demo

.claude/rules/documentation.md

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,32 @@ paths:
55

66
# Documentation Requirements
77

8-
The technical paper (`docs/paper/reductions.typ`) must include:
9-
10-
1. **Problem Definitions** — using `problem-def` wrapper
11-
2. **Reduction Theorems** — using `reduction-rule` function
12-
3. **Reduction Examples** — minimal working example showing reduce → solve → extract
8+
**Reference:** search `docs/paper/reductions.typ` for `MinimumVertexCover` `MaximumIndependentSet` to see a complete problem-def + reduction-rule example.
139

1410
## Adding a Problem Definition
1511

1612
```typst
17-
#problem-def("MaximumIndependentSet", "Maximum Independent Set (MIS)")[
13+
#problem-def("ProblemName")[
1814
Mathematical definition...
1915
]
2016
```
2117

22-
This auto-generates:
23-
- A label `<def:MaximumIndependentSet>` for cross-references
24-
- The problem's schema (fields from Rust struct)
25-
- The list of available reductions
26-
27-
Also add an entry to the `display-name` dictionary:
18+
Also add to the `display-name` dictionary:
2819
```typst
29-
"MaximumIndependentSet": "MIS",
20+
"ProblemName": [Problem Name],
3021
```
3122

3223
## Adding a Reduction Theorem
3324

3425
```typst
35-
#reduction-rule(
36-
"MaximumIndependentSet", "QUBO",
37-
example: "maximumindependentset_to_qubo",
38-
overhead: (n: 0, m: 1),
26+
#reduction-rule("Source", "Target",
27+
example: true,
28+
example-caption: [caption text],
3929
)[
30+
Rule statement...
31+
][
4032
Proof sketch...
4133
]
4234
```
4335

44-
This auto-generates:
45-
- A theorem label `<thm:MaximumIndependentSet-to-QUBO>`
46-
- References to source/target problem definitions (if they exist)
47-
- Registration in `covered-rules` state for completeness checking
48-
- The example code block from `examples/reduction_<example>.rs`
49-
50-
## Completeness Warnings
51-
52-
The paper auto-checks completeness:
53-
- After Problem Definitions: warns if JSON graph nodes are missing from `display-name`
54-
- After Reductions section: warns if JSON graph edges are missing from `covered-rules`
36+
Every directed reduction in the graph needs its own `reduction-rule` entry. The paper auto-checks completeness against `reduction_graph.json`.

.claude/rules/testing.md

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,18 @@
11
# Testing Requirements
22

3-
## Coverage Requirement
4-
New code must have >95% test coverage.
3+
**Reference test:** `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs`
54

6-
```bash
7-
# Check coverage for specific module
8-
cargo tarpaulin --features ilp --skip-clean --ignore-tests -- <module_name>
5+
## Coverage
96

10-
# Generate full HTML report
11-
make coverage
12-
```
7+
New code must have >95% test coverage. Run `make coverage` to check.
8+
9+
## Naming
1310

14-
## Test Naming Conventions
1511
- Reduction tests: `test_<source>_to_<target>_closed_loop`
1612
- Model tests: `test_<model>_basic`, `test_<model>_serialization`
1713
- Solver tests: `test_<solver>_<problem>`
1814

19-
## Closed-Loop Test Pattern
20-
Every reduction MUST have a closed-loop test:
21-
22-
```rust
23-
#[test]
24-
fn test_source_to_target_closed_loop() {
25-
// 1. Create small instance
26-
let problem = SourceProblem::new(...);
27-
28-
// 2. Reduce
29-
let reduction = problem.reduce_to::<TargetProblem>();
30-
let target = reduction.target_problem();
31-
32-
// 3. Solve target
33-
let solver = BruteForce::new();
34-
let solutions = solver.find_best(target);
35-
36-
// 4. Extract and verify
37-
for sol in solutions {
38-
let extracted = reduction.extract_solution(&sol);
39-
assert!(problem.is_valid_solution(&extracted));
40-
}
41-
}
42-
```
43-
44-
## Before Submitting PR
45-
```bash
46-
make test # All tests pass
47-
make clippy # No warnings
48-
make coverage # >95% for new code
49-
```
50-
51-
## Test File Organization
15+
## File Organization
5216

5317
Unit tests live in `src/unit_tests/`, mirroring `src/` structure. Source files reference them via `#[path]`:
5418

@@ -59,12 +23,10 @@ Unit tests live in `src/unit_tests/`, mirroring `src/` structure. Source files r
5923
mod tests;
6024
```
6125

62-
The `#[path]` is relative to the source file's directory. `use super::*` in the test file resolves to the parent module (same as inline tests).
26+
Integration tests are in `tests/suites/`, consolidated through `tests/main.rs`.
6327

64-
Integration tests are consolidated into a single binary at `tests/main.rs`, with test modules in `tests/suites/`.
28+
## Before PR
6529

66-
## Anti-patterns
67-
- Don't skip closed-loop tests for reductions
68-
- Don't test only happy paths - include edge cases
69-
- Don't ignore clippy warnings
70-
- Don't add inline `mod tests` blocks in `src/` — use `src/unit_tests/` with `#[path]`
30+
```bash
31+
make test clippy
32+
```

0 commit comments

Comments
 (0)