diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
index c414fae9b..9d567178d 100644
--- a/.claude/CLAUDE.md
+++ b/.claude/CLAUDE.md
@@ -56,7 +56,7 @@ Problem (core trait — all problems must implement)
 ├── type Metric: Clone                 // SolutionSize<W> for optimization, bool for satisfaction
 ├── fn dims(&self) -> Vec<usize>       // config space: [2, 2, 2] for 3 binary variables
 ├── fn evaluate(&self, config) -> Metric
-├── fn variant() -> Vec<(&str, &str)>  // [("graph","SimpleGraph"), ("weight","i32")]
+├── fn variant() -> Vec<(&str, &str)>  // e.g., [("graph","SimpleGraph"), ("weight","i32")]
 └── fn num_variables(&self) -> usize   // default: dims().len()
 
 OptimizationProblem : Problem<Metric = SolutionSize<Self::Value>> (extension for optimization)
@@ -74,7 +74,7 @@ enum Direction { Maximize, Minimize }
 ```
 
 ### Key Patterns
-- Problems parameterized by weight type `W` and graph type `G`
+- Problems parameterized by graph type `G` and optionally weight type `W` (problem-dependent)
 - `ReductionResult` provides `target_problem()` and `extract_solution()`
 - `Solver::find_best()` → `Option<Vec<usize>>` for optimization problems; `Solver::find_satisfying()` → `Option<Vec<usize>>` for `Metric = bool`
 - `BruteForce::find_all_best()` / `find_all_satisfying()` return `Vec<Vec<usize>>` for all optimal/satisfying solutions
@@ -111,90 +111,6 @@ Reduction graph nodes use variant IDs: `ProblemName[/GraphType][/Weighted]`
 - Completeness warnings auto-check that all JSON graph nodes/edges are covered in the paper
 - `display-name` dict maps `ProblemName` to display text
 
-## Adding a Reduction Rule (A -> B)
-
-**Reference implementations — read these first:**
-- **Reduction rule:** `src/rules/minimumvertexcover_maximumindependentset.rs` — `ReductionResult` + `ReduceTo` + `#[reduction]` macro
-- **Unit test:** `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs` — closed-loop + edge cases
-- **Example program:** `examples/reduction_minimumvertexcover_to_maximumindependentset.rs` — create, reduce, solve, extract, verify, export
-- **Paper entry:** `docs/paper/reductions.typ` (search for `MinimumVertexCover` `MaximumIndependentSet`)
-- **Traits:** `src/rules/traits.rs` — `ReductionResult` and `ReduceTo` trait definitions
-
-### 0. Before Writing Code
-
-1. **Ensure you have enough information**
-   - The reduction algorithm, from reliable source, e.g. a paper or a famous website.
-   - Which example instance to use in `examples/`, example is expected for human reading.
-   - The method to generate test data in `tests/data/<target>/` as json files.
-
-   otherwise use `superpowers:brainstorming` to discuss with the user.
-2. **Write plan** — save to `docs/plans/` using `superpowers:writing-plans`.
-
-### 1. Implement
-
-Create `src/rules/<source>_<target>.rs` following the reference. Key pieces:
-
-- **`ReductionResult` struct + impl** — `target_problem()` + `extract_solution()` (see reference)
-- **`ReduceTo` impl with `#[reduction(...)]` macro** — auto-generates `inventory::submit!`; only `overhead` attribute needed (graph/weight types are inferred, defaulting to `SimpleGraph`/`Unweighted`)
-- **`#[cfg(test)] #[path = ...]`** linking to unit tests
-
-Register in `src/rules/mod.rs`.
-
-### 2. Test
-
-- **Unit tests** in `src/unit_tests/rules/<source>_<target>.rs` — closed-loop + edge cases (see reference test).
-- **Integration tests** in `tests/suites/reductions.rs` — compare against JSON ground truth.
-
-### 3. Example Program
-
-Add `examples/reduction_<source>_to_<target>.rs` — create, reduce, solve, extract, verify, export JSON (see reference example).
-
-Examples must expose `pub fn run()` with `fn main() { run() }` so they can be tested directly via `include!` (no subprocess). Use regular comments (`//`) not inner doc comments (`//!`), and hardcode the example name instead of using `env!("CARGO_BIN_NAME")`.
-
-Register the example in `tests/suites/examples.rs` by adding:
-```rust
-example_test!(reduction_<source>_to_<target>);
-example_fn!(test_<source>_to_<target>, reduction_<source>_to_<target>);
-```
-
-### 4. Document
-
-Update `docs/paper/reductions.typ` — add `reduction-rule("Source", "Target", ...)` with proof sketch (see Documentation Requirements section below).
-
-### 5. Regenerate Graph
-
-```bash
-cargo run --example export_graph
-```
-
-## Adding a Model (Problem Type)
-
-**Reference implementations — read these first:**
-- **Optimization problem:** `src/models/graph/maximum_independent_set.rs` — `Problem` + `OptimizationProblem` with `Metric = SolutionSize<W>`
-- **Satisfaction problem:** `src/models/satisfiability/sat.rs` — `Problem` with `Metric = bool`
-- **Reference test:** `src/unit_tests/models/graph/maximum_independent_set.rs`
-
-### Steps
-
-1. **Create** `src/models/<category>/<name>.rs` — follow the reference for struct definition, `Problem` impl, and `OptimizationProblem` impl (if applicable).
-2. **Register** in `src/models/<category>/mod.rs`.
-3. **Add tests** in `src/unit_tests/models/<category>/<name>.rs` (linked via `#[path]`).
-4. **Document** in `docs/paper/reductions.typ`: add `display-name` entry and `#problem-def("Name")[definition...]`.
-
-### Trait Implementations
-
-See Trait Hierarchy above for `Problem` and `OptimizationProblem` members. Weight management (`weights()`, `set_weights()`, `is_weighted()`) goes on inherent `impl` blocks, not traits. See the reference implementation for the pattern.
-
-### Categories
-
-- `src/models/satisfiability/` — Satisfiability, KSatisfiability
-- `src/models/graph/` — MaximumIndependentSet, MinimumVertexCover, KColoring, etc.
-- `src/models/set/` — MinimumSetCovering, MaximumSetPacking
-- `src/models/optimization/` — SpinGlass, QUBO, ILP
-- `src/models/specialized/` — CircuitSAT, Factoring, PaintShop, BicliqueCover, BMF
-
-Naming convention: see Problem Names above.
-
 ## Testing Requirements
 
 **Reference implementations — read these first:**
@@ -219,7 +135,7 @@ See Key Patterns above for solver API signatures. Follow the reference files for
 
 ### File Organization
 
-Unit tests in `src/unit_tests/` linked via `#[path]` (see Core Modules above). Integration tests in `tests/suites/`, consolidated through `tests/main.rs`. Example tests in `tests/suites/examples.rs` (see Example Program in Adding a Reduction above).
+Unit tests in `src/unit_tests/` linked via `#[path]` (see Core Modules above). Integration tests in `tests/suites/`, consolidated through `tests/main.rs`. Example tests in `tests/suites/examples.rs` using `include!` for direct invocation.
 
 ## Documentation Requirements
 
diff --git a/.claude/skills/issue-to-pr.md b/.claude/skills/issue-to-pr.md
index 29de3b640..bf50be6aa 100644
--- a/.claude/skills/issue-to-pr.md
+++ b/.claude/skills/issue-to-pr.md
@@ -72,7 +72,74 @@ If the reference is a paper or textbook, search for accessible summaries, lectur
 
 ### 5. Write Plan
 
-Write plan to `docs/plans/YYYY-MM-DD-<slug>.md` using `superpowers:writing-plans`:
+Write plan to `docs/plans/YYYY-MM-DD-<slug>.md` using `superpowers:writing-plans`.
+
+The plan MUST include an **action pipeline** section with concrete steps based on issue type.
+
+#### For `[Rule]` issues (A -> B reduction)
+
+**Reference implementations — read these first:**
+- Reduction rule: `src/rules/minimumvertexcover_maximumindependentset.rs`
+- Unit test: `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs`
+- Example program: `examples/reduction_minimumvertexcover_to_maximumindependentset.rs`
+- Paper entry: search `docs/paper/reductions.typ` for `MinimumVertexCover` `MaximumIndependentSet`
+- Traits: `src/rules/traits.rs`
+
+**Action pipeline:**
+
+1. **Implement reduction** — Create `src/rules/<source>_<target>.rs`:
+   - `ReductionResult` struct + impl (`target_problem()` + `extract_solution()`)
+   - `ReduceTo` impl with `#[reduction(...)]` macro (only `overhead` attribute needed)
+   - `#[cfg(test)] #[path = ...]` linking to unit tests
+   - Register in `src/rules/mod.rs`
+
+2. **Write unit tests** — Create `src/unit_tests/rules/<source>_<target>.rs`:
+   - Closed-loop test: create source → reduce → solve target → extract → verify
+   - Edge cases
+
+3. **Write example program** — Create `examples/reduction_<source>_to_<target>.rs`:
+   - Must have `pub fn run()` + `fn main() { run() }`
+   - Use regular comments (`//`), hardcode example name
+   - Create, reduce, solve, extract, verify, export JSON
+   - Register in `tests/suites/examples.rs`
+
+4. **Document in paper** — Update `docs/paper/reductions.typ`:
+   - Add `reduction-rule("Source", "Target", ...)` with proof sketch
+   - Present example in tutorial style (see KColoring→QUBO section for reference)
+
+5. **Regenerate graph** — `cargo run --example export_graph`
+
+**Rules for solver implementation:**
+- Make sure at least one solver is provided in the issue template. Check if the solving strategy is valid. If not, reply under issue to ask for clarification.
+- If the solver uses integer programming, implement the model and ILP reduction rule together.
+- Otherwise, ensure the information provided is enough to implement a solver.
+
+**Rules for example writing:**
+- Implement the user-provided example instance as an example program in `examples/`.
+- Run the example; verify JSON output against user-provided information.
+- Present in `docs/paper/reductions.typ` in tutorial style with clear intuition (see KColoring→QUBO section for reference).
+
+#### For `[Model]` issues
+
+**Reference implementations — read these first:**
+- Optimization problem: `src/models/graph/maximum_independent_set.rs`
+- Satisfaction problem: `src/models/satisfiability/sat.rs`
+- Reference test: `src/unit_tests/models/graph/maximum_independent_set.rs`
+
+**Action pipeline:**
+
+1. **Implement model** — Create `src/models/<category>/<name>.rs`:
+   - Struct definition, `Problem` impl, `OptimizationProblem` impl (if applicable)
+   - Weight management via inherent methods (`weights()`, `set_weights()`, `is_weighted()`), not traits
+   - Register in `src/models/<category>/mod.rs`
+
+2. **Write tests** — Create `src/unit_tests/models/<category>/<name>.rs`:
+   - Basic evaluation tests, serialization tests
+   - Link via `#[path]`
+
+3. **Document** — Update `docs/paper/reductions.typ`:
+   - Add `display-name` entry
+   - Add `#problem-def("Name")[definition...]`
 
 ### 6. Create PR
 
diff --git a/.github/ISSUE_TEMPLATE/problem.md b/.github/ISSUE_TEMPLATE/problem.md
index 0ed94f5b3..47763dbc1 100644
--- a/.github/ISSUE_TEMPLATE/problem.md
+++ b/.github/ISSUE_TEMPLATE/problem.md
@@ -55,10 +55,10 @@ Connect fields to the symbols defined above.
 ## How to solve
 <!--
 Solver is required for reduction rule verification purpose.
-- Can it be solved by (existing) bruteforce?
-- Can it be solved by reducing the integer programming? If so, how to reduce?
-- If none apply
 -->
+- [ ] It can be solved by (existing) bruteforce.
+- [ ] It can be solved by reducing the integer programming, through #issue-number (please file a new issue it is not exist).
+- [ ] Other, refer to ... 
 
 ## Example Instance
 
diff --git a/.github/ISSUE_TEMPLATE/rule.md b/.github/ISSUE_TEMPLATE/rule.md
index dd387a410..6395d33a5 100644
--- a/.github/ISSUE_TEMPLATE/rule.md
+++ b/.github/ISSUE_TEMPLATE/rule.md
@@ -43,7 +43,7 @@ E.g.
 - Use external solver to cross-check
 -->
 
-## Example Source Instance
+## Example
 
 <!-- A small but non-trivial source instance for the paper illustration.
 Must be small enough for brute-force solving, but large enough to exercise the reduction meaningfully. E.g. "petersen graph: |V|=10, |E|=15, 3-regular" should be perfect.
diff --git a/README.md b/README.md
index 601132e94..b96b7823c 100644
--- a/README.md
+++ b/README.md
@@ -54,11 +54,10 @@ assert_eq!(solution.iter().sum::<usize>(), 2); // Max IS size is 2
 
 1. **Open an issue** using the [Problem](https://github.com/CodingThrust/problem-reductions/issues/new?template=problem.md) or [Rule](https://github.com/CodingThrust/problem-reductions/issues/new?template=rule.md) template. Fill in all sections — the templates guide you through the required information (definition, algorithm, size overhead, example instance, etc.).
 
-2. Optionally, if you prefer to make a **concrete plan** or **implement yourself**, I will recommend you to use the [superpowers:brainstorming](https://github.com/obra/superpowers) skill to help you write a detailed plan. After making implementation plan, you can either implement the plan yourself or create a PR with prompt:
-   ```
-   Create a pull request starting with "[action]" in the description.
-   ```
-   to trigger automated implementation.
+2. Our AI agents will pick-up the issue and generate a plan to implement the reduction rule.
+3. You will be mentioned in the pull request, provide feedback to the AI agents. If you are satisfied with the plan, you can merge the PR.
+
+Optionally, if you prefer to **implement yourself**, I will recommend you to use the [superpowers:brainstorming](https://github.com/obra/superpowers) skill to help you write a detailed plan. Create a PR and let maintainers help review and merge the PR.
 
 ### Developer Commands
 
diff --git a/benches/solver_benchmarks.rs b/benches/solver_benchmarks.rs
index b62fcdab2..8d3589a81 100644
--- a/benches/solver_benchmarks.rs
+++ b/benches/solver_benchmarks.rs
@@ -138,7 +138,7 @@ fn bench_coloring(c: &mut Criterion) {
 
     for n in [3, 4, 5, 6].iter() {
         let edges: Vec<(usize, usize)> = (0..*n - 1).map(|i| (i, i + 1)).collect();
-        let problem = KColoring::<3, SimpleGraph, i32>::new(*n, edges);
+        let problem = KColoring::<3, SimpleGraph>::new(*n, edges);
         let solver = BruteForce::new();
 
         group.bench_with_input(BenchmarkId::new("path_3colors", n), n, |b, _| {
diff --git a/docs/paper/lib.typ b/docs/paper/lib.typ
new file mode 100644
index 000000000..9de772697
--- /dev/null
+++ b/docs/paper/lib.typ
@@ -0,0 +1,121 @@
+// Graph visualization library for the problem-reductions paper
+#import "@preview/cetz:0.4.2": canvas, draw
+
+// ── Style defaults ─────────────────────────────────────────────
+
+// Color palette for k-coloring visualizations
+#let graph-colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"))
+
+// Weight-based fill colors for grid graph nodes
+#let weight-color(w) = if w == 1 { blue } else if w == 2 { red } else { green }
+
+// ── Primitives: g-node, g-edge ─────────────────────────────────
+// All graph drawing goes through these two functions.
+// They define the standard style; callers can override any parameter.
+
+// Draw a single graph node.
+//   pos: (x, y) position
+//   name: CetZ element name (for edge references)
+//   label: none or content to place inside the node
+#let g-node(
+  pos,
+  name: none,
+  radius: 0.2,
+  fill: white,
+  stroke: 0.5pt,
+  label: none,
+  label-size: 8pt,
+) = {
+  draw.circle(pos, radius: radius, fill: fill, stroke: stroke, name: name)
+  if label != none {
+    draw.content(name, text(label-size, label))
+  }
+}
+
+// Draw a single graph edge between two named nodes or positions.
+#let g-edge(
+  from,
+  to,
+  stroke: 1pt + black,
+) = {
+  draw.line(from, to, stroke: stroke)
+}
+
+// ── Pre-defined graph layouts ──────────────────────────────────
+// Each returns (vertices: [...], edges: [...])
+
+// Petersen graph: outer pentagon (0-4) + inner star (5-9)
+#let petersen-graph() = {
+  let r-outer = 1.2
+  let r-inner = 0.6
+  let vertices = ()
+  for i in range(5) {
+    let angle = 90deg - i * 72deg
+    vertices.push((calc.cos(angle) * r-outer, calc.sin(angle) * r-outer))
+  }
+  for i in range(5) {
+    let angle = 90deg - i * 72deg
+    vertices.push((calc.cos(angle) * r-inner, calc.sin(angle) * r-inner))
+  }
+  let edges = (
+    (0,1),(0,4),(0,5),(1,2),(1,6),(2,3),(2,7),(3,4),(3,8),(4,9),
+    (5,7),(5,8),(6,8),(6,9),(7,9),
+  )
+  (vertices: vertices, edges: edges)
+}
+
+// House graph: square base (0-1-3-2) + triangle roof (2-3-4)
+#let house-graph() = {
+  let vertices = ((0, 0), (1, 0), (0, 1), (1, 1), (0.5, 1.7))
+  let edges = ((0,1),(0,2),(1,3),(2,3),(2,4),(3,4))
+  (vertices: vertices, edges: edges)
+}
+
+// Octahedral graph (K_{2,2,2}): 6 vertices, 12 edges
+// Layout: top/bottom poles with 4 equatorial vertices
+#let octahedral-graph() = {
+  let vertices = (
+    (0, -1.2),   // 0: bottom pole
+    (-1.0, 0),   // 1: left
+    (0, 0.5),    // 2: upper-center
+    (0, -0.5),   // 3: lower-center
+    (1.0, 0),    // 4: right
+    (0, 1.2),    // 5: top pole
+  )
+  let edges = (
+    (0,1),(0,2),(0,3),(0,4),(1,2),(1,3),(1,5),(2,4),(2,5),(3,4),(3,5),(4,5),
+  )
+  (vertices: vertices, edges: edges)
+}
+
+// ── Grid graph functions (JSON-driven) ─────────────────────────
+// Extract positions from JSON, draw with dense styling via g-node/g-edge.
+
+// King's subgraph from JSON with weight-based coloring
+#let draw-grid-graph(data, cell-size: 0.2) = canvas(length: 1cm, {
+  let grid-data = data.grid_graph
+  let positions = grid-data.nodes.map(n => (n.col * cell-size, -n.row * cell-size))
+  let fills = grid-data.nodes.map(n => weight-color(n.weight))
+  let edges = grid-data.edges.map(e => (e.at(0), e.at(1)))
+  for (u, v) in edges { g-edge(positions.at(u), positions.at(v), stroke: 0.4pt + gray) }
+  for (k, pos) in positions.enumerate() {
+    g-node(pos, radius: 0.04, stroke: none, fill: fills.at(k))
+  }
+})
+
+// Triangular lattice from JSON with weight-based coloring
+// Matches Rust GridGraph::physical_position_static for Triangular (offset_even_cols=true)
+#let draw-triangular-graph(data, cell-size: 0.2) = canvas(length: 1cm, {
+  let grid-data = data.grid_graph
+  let sqrt3_2 = calc.sqrt(3) / 2
+  let positions = grid-data.nodes.map(n => {
+    let offset = if calc.rem(n.col, 2) == 0 { 0.5 } else { 0.0 }
+    ((n.row + offset) * cell-size, -n.col * sqrt3_2 * cell-size)
+  })
+  let fills = grid-data.nodes.map(n => weight-color(n.weight))
+  let edges = grid-data.edges.map(e => (e.at(0), e.at(1)))
+  for (u, v) in edges { g-edge(positions.at(u), positions.at(v), stroke: 0.3pt + gray) }
+  for (k, pos) in positions.enumerate() {
+    g-node(pos, radius: 0.025, stroke: none, fill: fills.at(k))
+  }
+})
diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ
index 59aa31454..eb718c9ef 100644
--- a/docs/paper/reductions.typ
+++ b/docs/paper/reductions.typ
@@ -2,6 +2,7 @@
 #let graph-data = json("../src/reductions/reduction_graph.json")
 #import "@preview/cetz:0.4.2": canvas, draw
 #import "@preview/ctheorems:1.1.3": thmbox, thmplain, thmproof, thmrules
+#import "lib.typ": g-node, g-edge, petersen-graph, house-graph, octahedral-graph, draw-grid-graph, draw-triangular-graph, graph-colors
 
 #set page(paper: "a4", margin: (x: 2cm, y: 2.5cm))
 #set text(font: "New Computer Modern", size: 10pt)
@@ -513,7 +514,36 @@ where $P$ is a penalty weight large enough that any constraint violation costs m
   Summing over all edges, each vertex $i$ appears in $"deg"(i)$ terms. The QUBO coefficients are: diagonal $Q_(i i) = w_i - P dot "deg"(i)$ (objective plus linear penalty), off-diagonal $Q_(i j) = P$ for edges. The constant $P |E|$ does not affect the minimizer.
 ]
 
-#reduction-rule("KColoring", "QUBO")[
+#let kc_qubo = load-example("kcoloring_to_qubo")
+#let kc_qubo_r = load-results("kcoloring_to_qubo")
+#let kc_qubo_sol = kc_qubo_r.solutions.at(0)
+#reduction-rule("KColoring", "QUBO",
+  example: true,
+  example-caption: [House graph ($n = 5$, $|E| = 6$, $chi = 3$) with $k = 3$ colors],
+  extra: [
+    #{
+      let hg = house-graph()
+      let fills = kc_qubo_sol.source_config.map(c => graph-colors.at(c))
+      align(center, canvas(length: 0.8cm, {
+        for (u, v) in hg.edges { g-edge(hg.vertices.at(u), hg.vertices.at(v)) }
+        for (k, pos) in hg.vertices.enumerate() {
+          g-node(pos, name: str(k), fill: fills.at(k), label: str(k))
+        }
+      }))
+    }
+
+    *Step 1 -- Encode colors as binary variables.* Each vertex $v in {0,...,4}$ gets $k = 3$ binary variables $(x_(v,0), x_(v,1), x_(v,2))$, where $x_(v,c) = 1$ means "vertex $v$ receives color $c$." This gives $n k = 5 times 3 = 15$ QUBO variables total, arranged as:
+    $ underbrace(x_(0,0) x_(0,1) x_(0,2), "vertex 0") #h(4pt) underbrace(x_(1,0) x_(1,1) x_(1,2), "vertex 1") #h(4pt) dots.c #h(4pt) underbrace(x_(4,0) x_(4,1) x_(4,2), "vertex 4") $
+
+    *Step 2 -- One-hot penalty.* Each vertex must receive _exactly one_ color, i.e.\ $sum_c x_(v,c) = 1$. The penalty $(1 - sum_c x_(v,c))^2$ is zero iff exactly one variable in the group is 1. With weight $P_1 = 1 + n = 6$, this contributes $Q_(v k+c, v k+c) = -6$ on the diagonal and $Q_(v k+c_1, v k+c_2) = 12$ between same-vertex color pairs. These are the $5 times 5$ diagonal blocks of $Q$.\
+
+    *Step 3 -- Edge conflict penalty.* For each edge $(u,v) in E$ and each color $c$, both endpoints having color $c$ is penalized: $P_2 dot x_(u,c) x_(v,c)$ with $P_2 = P_1 slash 2 = 3$. The house has 6 edges, each contributing 3 color penalties $arrow.r$ 18 off-diagonal entries of value $3$ in $Q$.\
+
+    *Step 4 -- Verify a solution.* The first valid 3-coloring is $(c_0, ..., c_4) = (#kc_qubo_sol.source_config.map(str).join(", "))$, shown in the figure above. The one-hot encoding is $bold(x) = (#kc_qubo_sol.target_config.map(str).join(", "))$. Check: each 3-bit group has exactly one 1 (valid one-hot #sym.checkmark), and for every edge the two endpoints have different colors (e.g.\ edge $0 dash 1$: colors $#kc_qubo_sol.source_config.at(0), #kc_qubo_sol.source_config.at(1)$ #sym.checkmark).\
+
+    *Count:* #kc_qubo_r.solutions.len() valid colorings $= 3! times 3$. The triangle $2 dash 3 dash 4$ forces 3 distinct colors ($3! = 6$ permutations); for each, the base vertices $0, 1$ each have 3 compatible choices but share edge $0 dash 1$, leaving $3$ valid pairs.
+  ],
+)[
   Given $G = (V, E)$ with $k$ colors, construct upper-triangular $Q in RR^(n k times n k)$ using one-hot encoding $x_(v,c) in {0,1}$ ($n k$ variables indexed by $v dot k + c$).
 ][
   _Construction._ Applying the penalty method (@sec:penalty-method), the QUBO objective combines a one-hot constraint penalty and an edge conflict penalty:
@@ -807,119 +837,28 @@ The following reductions to Integer Linear Programming are straightforward formu
 #let square_unweighted = json("petersen_square_unweighted.json")
 #let triangular_mapping = json("petersen_triangular.json")
 
-// Draw Petersen graph with standard layout
-#let draw-petersen-cetz(data) = canvas(length: 1cm, {
-  import draw: *
-  let r-outer = 1.2
-  let r-inner = 0.6
-
-  // Positions: outer pentagon (0-4), inner star (5-9)
-  let positions = ()
-  for i in range(5) {
-    let angle = 90deg - i * 72deg
-    positions.push((calc.cos(angle) * r-outer, calc.sin(angle) * r-outer))
-  }
-  for i in range(5) {
-    let angle = 90deg - i * 72deg
-    positions.push((calc.cos(angle) * r-inner, calc.sin(angle) * r-inner))
-  }
-
-  // Draw edges
-  for edge in data.edges {
-    let (u, v) = (edge.at(0), edge.at(1))
-    line(positions.at(u), positions.at(v), stroke: 0.6pt + gray)
-  }
-
-  // Draw nodes
-  for (k, pos) in positions.enumerate() {
-    circle(pos, radius: 0.12, fill: blue, stroke: none)
-  }
-})
-
-// Draw King's Subgraph from JSON nodes - uses pre-computed edges
-#let draw-grid-cetz(data, cell-size: 0.2) = canvas(length: 1cm, {
-  import draw: *
-  let grid-data = data.grid_graph
-
-  // Get node positions (col, row) for drawing
-  let grid-positions = grid-data.nodes.map(n => (n.col, n.row))
-  let weights = grid-data.nodes.map(n => n.weight)
-
-  // Use pre-computed edges from JSON
-  let edges = grid-data.edges
-
-  // Scale for drawing
-  let vertices = grid-positions.map(p => (p.at(0) * cell-size, -p.at(1) * cell-size))
-
-  // Draw edges
-  for edge in edges {
-    let (k, l) = (edge.at(0), edge.at(1))
-    line(vertices.at(k), vertices.at(l), stroke: 0.4pt + gray)
-  }
-
-  // Draw nodes with weight-based color
-  for (k, pos) in vertices.enumerate() {
-    let w = weights.at(k)
-    let color = if w == 1 { blue } else if w == 2 { red } else { green }
-    circle(pos, radius: 0.04, fill: color, stroke: none)
-  }
-})
-
-// Draw triangular lattice from JSON nodes - uses pre-computed edges
-// Matches Rust's GridGraph physical_position_static for Triangular with offset_even_cols=true:
-//   x = row + offset (where offset = 0.5 if col is even)
-//   y = col * sqrt(3)/2
-#let draw-triangular-cetz(data, cell-size: 0.2) = canvas(length: 1cm, {
-  import draw: *
-  let grid-data = data.grid_graph
-
-  // Get node positions with triangular geometry for drawing
-  // Match Rust GridGraph::physical_position_static for Triangular:
-  //   x = row + 0.5 (if col is even, since offset_even_cols=true)
-  //   y = col * sqrt(3)/2
-  let sqrt3_2 = calc.sqrt(3) / 2
-  let grid-positions = grid-data.nodes.map(n => {
-    let offset = if calc.rem(n.col, 2) == 0 { 0.5 } else { 0.0 }
-    let x = n.row + offset
-    let y = n.col * sqrt3_2
-    (x, y)
-  })
-  let weights = grid-data.nodes.map(n => n.weight)
-
-  // Use pre-computed edges from JSON
-  let edges = grid-data.edges
-
-  // Scale for drawing
-  let vertices = grid-positions.map(p => (p.at(0) * cell-size, -p.at(1) * cell-size))
-
-  // Draw edges
-  for edge in edges {
-    let (k, l) = (edge.at(0), edge.at(1))
-    line(vertices.at(k), vertices.at(l), stroke: 0.3pt + gray)
-  }
-
-  // Draw nodes with weight-based color
-  for (k, pos) in vertices.enumerate() {
-    let w = weights.at(k)
-    let color = if w == 1 { blue } else if w == 2 { red } else { green }
-    circle(pos, radius: 0.025, fill: color, stroke: none)
-  }
-})
-
 #figure(
   grid(
     columns: 3,
     gutter: 1.5em,
     align(center + horizon)[
-      #draw-petersen-cetz(petersen)
+      #{
+        let pg = petersen-graph()
+        canvas(length: 1cm, {
+          for (u, v) in pg.edges { g-edge(pg.vertices.at(u), pg.vertices.at(v)) }
+          for (k, pos) in pg.vertices.enumerate() {
+            g-node(pos, fill: blue, stroke: none)
+          }
+        })
+      }
       (a) Petersen graph
     ],
     align(center + horizon)[
-      #draw-grid-cetz(square_weighted)
+      #draw-grid-graph(square_weighted)
       (b) King's subgraph (weighted)
     ],
     align(center + horizon)[
-      #draw-triangular-cetz(triangular_mapping)
+      #draw-triangular-graph(triangular_mapping)
       (c) Triangular lattice (weighted)
     ],
   ),
diff --git a/docs/plans/2026-02-13-natural-variant-reductions-design.md b/docs/plans/2026-02-13-natural-variant-reductions-design.md
new file mode 100644
index 000000000..d2a40c26d
--- /dev/null
+++ b/docs/plans/2026-02-13-natural-variant-reductions-design.md
@@ -0,0 +1,125 @@
+# Natural Variant Reduction Edges
+
+## Problem
+
+The reduction graph export (`reduction_graph.json`) does not include "natural" reductions between variant nodes of the same problem. For example, `MaximumIndependentSet/GridGraph` should naturally reduce to `MaximumIndependentSet/SimpleGraph` because a GridGraph is a SimpleGraph. These edges should be auto-generated based on type hierarchies, not manually coded as `ReduceTo` impls.
+
+## Design
+
+### 1. Fix Graph Type Hierarchy (`src/graph_types.rs`)
+
+**Bug fix**: Remove incorrect `UnitDiskGraph => PlanarGraph` relationship.
+
+**Corrected hierarchy** (with all transitive relationships for compile-time trait bounds):
+
+```
+HyperGraph (most general)
+└── SimpleGraph
+    ├── PlanarGraph
+    ├── BipartiteGraph
+    └── UnitDiskGraph
+        └── GridGraph
+```
+
+Declarations:
+```rust
+declare_graph_subtype!(GridGraph => UnitDiskGraph);
+declare_graph_subtype!(GridGraph => SimpleGraph);       // transitive
+declare_graph_subtype!(GridGraph => HyperGraph);         // transitive
+declare_graph_subtype!(UnitDiskGraph => SimpleGraph);
+declare_graph_subtype!(UnitDiskGraph => HyperGraph);     // transitive
+declare_graph_subtype!(PlanarGraph => SimpleGraph);
+declare_graph_subtype!(PlanarGraph => HyperGraph);       // transitive
+declare_graph_subtype!(BipartiteGraph => SimpleGraph);
+declare_graph_subtype!(BipartiteGraph => HyperGraph);    // transitive
+declare_graph_subtype!(SimpleGraph => HyperGraph);
+```
+
+Add `GridGraph` and `HyperGraph` as `GraphMarker` types (marker structs + trait impls).
+
+### 2. Weight Type Hierarchy (new)
+
+Add `WeightSubtypeEntry` parallel to `GraphSubtypeEntry`:
+
+```rust
+pub struct WeightSubtypeEntry {
+    pub subtype: &'static str,
+    pub supertype: &'static str,
+}
+inventory::collect!(WeightSubtypeEntry);
+
+macro_rules! declare_weight_subtype {
+    ($sub:expr => $sup:expr) => {
+        inventory::submit! {
+            WeightSubtypeEntry {
+                subtype: $sub,
+                supertype: $sup,
+            }
+        }
+    };
+}
+
+declare_weight_subtype!("Unweighted" => "i32");
+declare_weight_subtype!("Unweighted" => "f64");  // transitive
+declare_weight_subtype!("i32" => "f64");
+```
+
+`ReductionGraph` builds `weight_hierarchy` using the same transitive closure algorithm as `graph_hierarchy`.
+
+### 3. Concrete Variant Registration (new)
+
+Add `ConcreteVariantEntry` to register problem+variant combinations that exist as concrete types but have no explicit reduction rules:
+
+```rust
+pub struct ConcreteVariantEntry {
+    pub name: &'static str,
+    pub variant: &'static [(&'static str, &'static str)],
+}
+inventory::collect!(ConcreteVariantEntry);
+```
+
+Register concrete variants in `register_types!` (or a new `register_variants!` section):
+
+```rust
+// For each problem that supports non-SimpleGraph types:
+submit_variant!("MaximumIndependentSet", &[("graph", "GridGraph"), ("weight", "Unweighted")]);
+submit_variant!("MaximumIndependentSet", &[("graph", "UnitDiskGraph"), ("weight", "Unweighted")]);
+submit_variant!("SpinGlass", &[("graph", "GridGraph"), ("weight", "f64")]);
+// etc.
+```
+
+### 4. Auto-generate Natural Edges in `to_json()`
+
+In `ReductionGraph::to_json()`, after collecting all nodes from reduction entries and concrete variant entries, add:
+
+```
+For every pair of nodes (A, B) with the same problem name:
+  If A != B AND for EVERY variant field:
+    field=="graph": A.graph is_subtype_of B.graph (using graph_hierarchy)
+    field=="weight": A.weight is_subtype_of B.weight (using weight_hierarchy)
+  Then emit a natural edge A -> B with:
+    - overhead: identity mapping (each output field = same input field)
+    - doc_path: "" (or a special marker like "natural")
+```
+
+### 5. Variant Comparison Rule
+
+A variant A is "more restrictive" than B when ALL fields satisfy the subtype relationship:
+- `graph`: checked against `graph_hierarchy`
+- `weight`: checked against `weight_hierarchy`
+- Other fields (e.g., `k`): must be equal (no hierarchy defined)
+
+### 6. Files Changed
+
+| File | Change |
+|------|--------|
+| `src/graph_types.rs` | Fix hierarchy, add GridGraph/HyperGraph markers, add WeightSubtypeEntry |
+| `src/rules/graph.rs` | Build weight_hierarchy, generate natural edges in to_json(), collect ConcreteVariantEntry nodes |
+| `src/rules/registry.rs` | Add ConcreteVariantEntry struct |
+| `examples/export_graph.rs` | No changes needed (uses ReductionGraph::to_json()) |
+
+### 7. Non-Goals
+
+- No new `ReduceTo` impls for natural reductions (these are visualization-only edges in the JSON)
+- No changes to runtime path finding (it already uses `rule_applicable` with graph hierarchy)
+- No changes to the paper/Typst documentation initially
diff --git a/docs/plans/2026-02-13-natural-variant-reductions-impl.md b/docs/plans/2026-02-13-natural-variant-reductions-impl.md
new file mode 100644
index 000000000..fc9d885e9
--- /dev/null
+++ b/docs/plans/2026-02-13-natural-variant-reductions-impl.md
@@ -0,0 +1,617 @@
+# Natural Variant Reduction Edges — Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Auto-generate natural reduction edges in the JSON export between variant nodes of the same problem type when all variant fields of the source are transitively more restrictive than the target.
+
+**Architecture:** Fix the graph type hierarchy (add GridGraph, HyperGraph; remove wrong UnitDiskGraph=>PlanarGraph). Add a parallel weight type hierarchy. Register concrete variant nodes. In `to_json()`, detect transitive reducibility between same-name nodes and emit natural edges.
+
+**Tech Stack:** Rust, inventory crate for compile-time collection, serde_json for export.
+
+---
+
+### Task 1: Fix Graph Type Hierarchy
+
+**Files:**
+- Modify: `src/graph_types.rs`
+- Modify: `src/unit_tests/graph_types.rs`
+
+**Step 1: Write failing tests for the corrected hierarchy**
+
+Add to `src/unit_tests/graph_types.rs`:
+
+```rust
+#[test]
+fn test_gridgraph_subtypes() {
+    fn assert_subtype<A: GraphSubtype<B>, B: GraphMarker>() {}
+
+    assert_subtype::<GridGraph, UnitDiskGraph>();
+    assert_subtype::<GridGraph, SimpleGraph>();
+    assert_subtype::<GridGraph, HyperGraph>();
+}
+
+#[test]
+fn test_hypergraph_subtypes() {
+    fn assert_subtype<A: GraphSubtype<B>, B: GraphMarker>() {}
+
+    assert_subtype::<SimpleGraph, HyperGraph>();
+    assert_subtype::<PlanarGraph, HyperGraph>();
+    assert_subtype::<UnitDiskGraph, HyperGraph>();
+    assert_subtype::<BipartiteGraph, HyperGraph>();
+    assert_subtype::<GridGraph, HyperGraph>();
+}
+
+#[test]
+fn test_gridgraph_entries_registered() {
+    let entries: Vec<_> = inventory::iter::<GraphSubtypeEntry>().collect();
+    assert!(entries
+        .iter()
+        .any(|e| e.subtype == "GridGraph" && e.supertype == "UnitDiskGraph"));
+}
+
+#[test]
+fn test_hypergraph_entries_registered() {
+    let entries: Vec<_> = inventory::iter::<GraphSubtypeEntry>().collect();
+    assert!(entries
+        .iter()
+        .any(|e| e.subtype == "SimpleGraph" && e.supertype == "HyperGraph"));
+}
+```
+
+Also update existing `test_declared_subtypes` to remove the `UnitDiskGraph, PlanarGraph` assertion and add new ones. Update `test_unit_disk_to_planar_registered` to assert it is NOT registered.
+
+**Step 2: Run tests to verify they fail**
+
+Run: `cargo test --lib graph_types`
+Expected: FAIL — `GridGraph` and `HyperGraph` not found as marker types.
+
+**Step 3: Implement the hierarchy changes**
+
+In `src/graph_types.rs`:
+
+1. Add `GridGraph` and `HyperGraph` marker types:
+```rust
+/// Grid graph - vertices on a grid, edges to neighbors.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct GridGraph;
+
+impl GraphMarker for GridGraph {}
+
+/// Hypergraph - most general graph type. Edges can connect any number of vertices.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct HyperGraph;
+
+impl GraphMarker for HyperGraph {}
+```
+
+2. Replace the hierarchy declarations:
+```rust
+// Corrected hierarchy:
+//   HyperGraph (most general)
+//   └── SimpleGraph
+//       ├── PlanarGraph
+//       ├── BipartiteGraph
+//       └── UnitDiskGraph
+//           └── GridGraph
+declare_graph_subtype!(GridGraph => UnitDiskGraph);
+declare_graph_subtype!(GridGraph => SimpleGraph);        // transitive
+declare_graph_subtype!(GridGraph => HyperGraph);          // transitive
+declare_graph_subtype!(UnitDiskGraph => SimpleGraph);
+declare_graph_subtype!(UnitDiskGraph => HyperGraph);      // transitive
+declare_graph_subtype!(PlanarGraph => SimpleGraph);
+declare_graph_subtype!(PlanarGraph => HyperGraph);        // transitive
+declare_graph_subtype!(BipartiteGraph => SimpleGraph);
+declare_graph_subtype!(BipartiteGraph => HyperGraph);     // transitive
+declare_graph_subtype!(SimpleGraph => HyperGraph);
+```
+
+**Step 4: Run tests to verify they pass**
+
+Run: `cargo test --lib graph_types`
+Expected: PASS
+
+**Step 5: Commit**
+
+```bash
+git add src/graph_types.rs src/unit_tests/graph_types.rs
+git commit -m "fix: correct graph type hierarchy, add GridGraph and HyperGraph markers"
+```
+
+---
+
+### Task 2: Add Weight Type Hierarchy
+
+**Files:**
+- Modify: `src/graph_types.rs`
+- Modify: `src/unit_tests/graph_types.rs`
+- Modify: `src/rules/graph.rs` (build weight_hierarchy)
+- Modify: `src/unit_tests/rules/graph.rs`
+
+**Step 1: Write failing tests**
+
+In `src/unit_tests/graph_types.rs`:
+```rust
+#[test]
+fn test_weight_subtype_entries_registered() {
+    let entries: Vec<_> = inventory::iter::<WeightSubtypeEntry>().collect();
+    assert!(entries
+        .iter()
+        .any(|e| e.subtype == "Unweighted" && e.supertype == "i32"));
+    assert!(entries
+        .iter()
+        .any(|e| e.subtype == "i32" && e.supertype == "f64"));
+    assert!(entries
+        .iter()
+        .any(|e| e.subtype == "Unweighted" && e.supertype == "f64"));
+}
+```
+
+In `src/unit_tests/rules/graph.rs`:
+```rust
+#[test]
+fn test_weight_hierarchy_built() {
+    let graph = ReductionGraph::new();
+    let hierarchy = graph.weight_hierarchy();
+    assert!(
+        hierarchy
+            .get("Unweighted")
+            .map(|s| s.contains("i32"))
+            .unwrap_or(false),
+        "Unweighted should have i32 as supertype"
+    );
+    assert!(
+        hierarchy
+            .get("i32")
+            .map(|s| s.contains("f64"))
+            .unwrap_or(false),
+        "i32 should have f64 as supertype"
+    );
+    assert!(
+        hierarchy
+            .get("Unweighted")
+            .map(|s| s.contains("f64"))
+            .unwrap_or(false),
+        "Unweighted should transitively have f64 as supertype"
+    );
+}
+```
+
+**Step 2: Run tests to verify they fail**
+
+Run: `cargo test --lib weight_subtype weight_hierarchy`
+Expected: FAIL — `WeightSubtypeEntry` does not exist.
+
+**Step 3: Implement weight hierarchy**
+
+In `src/graph_types.rs`, add:
+```rust
+/// Runtime registration of weight subtype relationships.
+pub struct WeightSubtypeEntry {
+    pub subtype: &'static str,
+    pub supertype: &'static str,
+}
+
+inventory::collect!(WeightSubtypeEntry);
+
+/// Macro to declare weight subtype relationships (runtime only, no compile-time trait).
+#[macro_export]
+macro_rules! declare_weight_subtype {
+    ($sub:expr => $sup:expr) => {
+        ::inventory::submit! {
+            $crate::graph_types::WeightSubtypeEntry {
+                subtype: $sub,
+                supertype: $sup,
+            }
+        }
+    };
+}
+
+declare_weight_subtype!("Unweighted" => "i32");
+declare_weight_subtype!("Unweighted" => "f64");  // transitive
+declare_weight_subtype!("i32" => "f64");
+```
+
+In `src/rules/graph.rs`, add `weight_hierarchy` field to `ReductionGraph` and build it in `new()`:
+```rust
+// In ReductionGraph struct:
+weight_hierarchy: HashMap<&'static str, HashSet<&'static str>>,
+
+// In new():
+let weight_hierarchy = Self::build_weight_hierarchy();
+
+// New method:
+fn build_weight_hierarchy() -> HashMap<&'static str, HashSet<&'static str>> {
+    let mut supertypes: HashMap<&'static str, HashSet<&'static str>> = HashMap::new();
+    for entry in inventory::iter::<WeightSubtypeEntry> {
+        supertypes.entry(entry.subtype).or_default().insert(entry.supertype);
+    }
+    // Same transitive closure as build_graph_hierarchy
+    loop {
+        let mut changed = false;
+        let types: Vec<_> = supertypes.keys().copied().collect();
+        for sub in &types {
+            let current: Vec<_> = supertypes.get(sub).map(|s| s.iter().copied().collect()).unwrap_or_default();
+            for sup in current {
+                if let Some(sup_supers) = supertypes.get(sup).cloned() {
+                    for ss in sup_supers {
+                        if supertypes.entry(sub).or_default().insert(ss) {
+                            changed = true;
+                        }
+                    }
+                }
+            }
+        }
+        if !changed { break; }
+    }
+    supertypes
+}
+
+// Public accessor:
+pub fn weight_hierarchy(&self) -> &HashMap<&'static str, HashSet<&'static str>> {
+    &self.weight_hierarchy
+}
+
+// Weight subtype check:
+pub fn is_weight_subtype(&self, sub: &str, sup: &str) -> bool {
+    sub == sup
+        || self.weight_hierarchy.get(sub).map(|s| s.contains(sup)).unwrap_or(false)
+}
+```
+
+**Step 4: Run tests to verify they pass**
+
+Run: `cargo test --lib weight_subtype weight_hierarchy`
+Expected: PASS
+
+**Step 5: Commit**
+
+```bash
+git add src/graph_types.rs src/unit_tests/graph_types.rs src/rules/graph.rs src/unit_tests/rules/graph.rs
+git commit -m "feat: add weight type hierarchy (Unweighted => i32 => f64)"
+```
+
+---
+
+### Task 3: Add Concrete Variant Registration
+
+**Files:**
+- Modify: `src/rules/registry.rs`
+- Modify: `src/rules/graph.rs` (register_types and to_json)
+- Modify: `src/unit_tests/rules/graph.rs`
+
+**Step 1: Write failing test**
+
+In `src/unit_tests/rules/graph.rs`:
+```rust
+#[test]
+fn test_concrete_variant_nodes_in_json() {
+    let graph = ReductionGraph::new();
+    let json = graph.to_json();
+
+    // GridGraph variants should appear as nodes
+    let gridgraph_node = json.nodes.iter().any(|n| {
+        n.name == "MaximumIndependentSet"
+            && n.variant.get("graph") == Some(&"GridGraph".to_string())
+    });
+    assert!(gridgraph_node, "MIS/GridGraph node should exist");
+}
+```
+
+**Step 2: Run test to verify it fails**
+
+Run: `cargo test --lib test_concrete_variant_nodes`
+Expected: FAIL — no GridGraph variant node exists.
+
+**Step 3: Implement ConcreteVariantEntry and registrations**
+
+In `src/rules/registry.rs`, add:
+```rust
+/// A registered concrete problem variant (for JSON export nodes).
+/// Variants registered here appear as nodes even without explicit reduction rules.
+pub struct ConcreteVariantEntry {
+    pub name: &'static str,
+    pub variant: &'static [(&'static str, &'static str)],
+}
+
+inventory::collect!(ConcreteVariantEntry);
+```
+
+In `src/rules/graph.rs`, in `register_types()`, add variant registrations after the existing `register!` block. Use `inventory::submit!` for each concrete variant that should appear:
+
+```rust
+fn register_variants() {
+    // These are registered via inventory::submit! at module level, not inside a function.
+    // See the submit! blocks below register_types.
+}
+```
+
+Actually, add the variant submissions at module level in `src/rules/graph.rs` (outside functions):
+
+```rust
+// Register concrete variants for graph problems that support non-SimpleGraph types.
+// These generate nodes in the JSON export.
+inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant: &[("graph", "GridGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant: &[("graph", "UnitDiskGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "MaxCut", variant: &[("graph", "GridGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "SpinGlass", variant: &[("graph", "GridGraph"), ("weight", "f64")] } }
+```
+
+In `to_json()`, collect nodes from `ConcreteVariantEntry` in addition to `ReductionEntry`:
+
+```rust
+// After collecting from ReductionEntry, also collect from ConcreteVariantEntry
+for entry in inventory::iter::<ConcreteVariantEntry> {
+    node_set.insert((
+        entry.name.to_string(),
+        Self::variant_to_map(entry.variant),
+    ));
+}
+```
+
+**Step 4: Run test to verify it passes**
+
+Run: `cargo test --lib test_concrete_variant_nodes`
+Expected: PASS
+
+**Step 5: Commit**
+
+```bash
+git add src/rules/registry.rs src/rules/graph.rs src/unit_tests/rules/graph.rs
+git commit -m "feat: add ConcreteVariantEntry for non-SimpleGraph variant nodes"
+```
+
+---
+
+### Task 4: Auto-Generate Natural Edges in `to_json()`
+
+**Files:**
+- Modify: `src/rules/graph.rs` (to_json method)
+- Modify: `src/unit_tests/rules/graph.rs`
+
+**Step 1: Write failing tests for natural edges**
+
+In `src/unit_tests/rules/graph.rs`:
+```rust
+#[test]
+fn test_natural_edge_graph_relaxation() {
+    let graph = ReductionGraph::new();
+    let json = graph.to_json();
+
+    // MIS/GridGraph -> MIS/SimpleGraph should exist (graph type relaxation)
+    let has_edge = json.edges.iter().any(|e| {
+        e.source.name == "MaximumIndependentSet"
+            && e.target.name == "MaximumIndependentSet"
+            && e.source.variant.get("graph") == Some(&"GridGraph".to_string())
+            && e.target.variant.get("graph") == Some(&"SimpleGraph".to_string())
+            && e.source.variant.get("weight") == e.target.variant.get("weight")
+    });
+    assert!(has_edge, "Natural edge MIS/GridGraph -> MIS/SimpleGraph should exist");
+}
+
+#[test]
+fn test_natural_edge_weight_promotion() {
+    let graph = ReductionGraph::new();
+    let json = graph.to_json();
+
+    // MIS{SimpleGraph, Unweighted} -> MIS{SimpleGraph, i32} should exist
+    let has_edge = json.edges.iter().any(|e| {
+        e.source.name == "MaximumIndependentSet"
+            && e.target.name == "MaximumIndependentSet"
+            && e.source.variant.get("graph") == Some(&"SimpleGraph".to_string())
+            && e.target.variant.get("graph") == Some(&"SimpleGraph".to_string())
+            && e.source.variant.get("weight") == Some(&"Unweighted".to_string())
+            && e.target.variant.get("weight") == Some(&"i32".to_string())
+    });
+    assert!(has_edge, "Natural edge MIS/Unweighted -> MIS/i32 should exist");
+}
+
+#[test]
+fn test_no_natural_edge_wrong_direction() {
+    let graph = ReductionGraph::new();
+    let json = graph.to_json();
+
+    // MIS/SimpleGraph -> MIS/GridGraph should NOT exist (wrong direction)
+    let has_edge = json.edges.iter().any(|e| {
+        e.source.name == "MaximumIndependentSet"
+            && e.target.name == "MaximumIndependentSet"
+            && e.source.variant.get("graph") == Some(&"SimpleGraph".to_string())
+            && e.target.variant.get("graph") == Some(&"GridGraph".to_string())
+    });
+    assert!(!has_edge, "Should NOT have MIS/SimpleGraph -> MIS/GridGraph");
+}
+
+#[test]
+fn test_no_natural_self_edge() {
+    let graph = ReductionGraph::new();
+    let json = graph.to_json();
+
+    // No self-edges (same node to same node)
+    for edge in &json.edges {
+        assert!(
+            edge.source != edge.target,
+            "Should not have self-edge: {} {:?}",
+            edge.source.name,
+            edge.source.variant
+        );
+    }
+}
+```
+
+**Step 2: Run tests to verify they fail**
+
+Run: `cargo test --lib test_natural_edge`
+Expected: FAIL — no natural edges exist yet.
+
+**Step 3: Implement natural edge generation**
+
+In `src/rules/graph.rs`, add a method to check if a variant is transitively reducible to another:
+
+```rust
+/// Check if variant A is strictly more restrictive than variant B (same problem name).
+/// Returns true if every field of A is a subtype of (or equal to) the corresponding field in B,
+/// and at least one field is strictly more restrictive.
+fn is_variant_reducible(
+    &self,
+    a: &std::collections::BTreeMap<String, String>,
+    b: &std::collections::BTreeMap<String, String>,
+) -> bool {
+    if a == b {
+        return false; // No self-reduction
+    }
+
+    let mut all_compatible = true;
+    let mut any_strict = false;
+
+    // Check all fields in both variants
+    let all_keys: std::collections::BTreeSet<_> = a.keys().chain(b.keys()).collect();
+
+    for key in all_keys {
+        let a_val = a.get(key.as_str()).map(|s| s.as_str()).unwrap_or("");
+        let b_val = b.get(key.as_str()).map(|s| s.as_str()).unwrap_or("");
+
+        if a_val == b_val {
+            continue; // Equal on this field
+        }
+
+        // Check subtype relationship based on field type
+        let is_sub = match key.as_str() {
+            "graph" => self.is_graph_subtype(a_val, b_val),
+            "weight" => self.is_weight_subtype(a_val, b_val),
+            _ => false, // Unknown fields must be equal
+        };
+
+        if is_sub {
+            any_strict = true;
+        } else {
+            all_compatible = false;
+            break;
+        }
+    }
+
+    all_compatible && any_strict
+}
+```
+
+In `to_json()`, after building `edges`, add natural edge generation:
+
+```rust
+// Auto-generate natural edges between same-name variants
+// Group nodes by name
+let mut nodes_by_name: HashMap<&str, Vec<&std::collections::BTreeMap<String, String>>> = HashMap::new();
+for (name, variant) in &node_set {
+    if !variant.is_empty() {
+        nodes_by_name.entry(name.as_str()).or_default().push(variant);
+    }
+}
+
+// For each pair of same-name nodes, check transitive reducibility
+for (name, variants) in &nodes_by_name {
+    for a in variants {
+        for b in variants {
+            if self.is_variant_reducible(a, b) {
+                let src_ref = VariantRef { name: name.to_string(), variant: (*a).clone() };
+                let dst_ref = VariantRef { name: name.to_string(), variant: (*b).clone() };
+                let key = (src_ref.clone(), dst_ref.clone());
+                if edge_set.insert(key) {
+                    // Identity overhead: each output field = same input field, p(x) = x
+                    let overhead: Vec<OverheadFieldJson> = a.iter()
+                        .filter(|(k, _)| *k != "graph" && *k != "weight")
+                        .map(|(k, _)| OverheadFieldJson { field: k.clone(), formula: k.clone() })
+                        .collect();
+                    // For graph problems, carry through standard size fields
+                    let overhead = if overhead.is_empty() {
+                        // Infer from existing edges of same problem
+                        // Fallback: emit common fields as identity
+                        vec![]  // Will be populated from source node's known fields
+                    } else {
+                        overhead
+                    };
+                    edges.push(EdgeJson {
+                        source: src_ref,
+                        target: dst_ref,
+                        overhead,
+                        doc_path: String::new(), // No module path — natural reduction
+                    });
+                }
+            }
+        }
+    }
+}
+
+// Re-sort edges after adding natural ones
+edges.sort_by(|a, b| { ... });
+```
+
+**Step 4: Run tests to verify they pass**
+
+Run: `cargo test --lib test_natural_edge`
+Expected: PASS
+
+**Step 5: Run full test suite**
+
+Run: `cargo test`
+Expected: PASS (existing tests may need minor adjustments for changed node/edge counts)
+
+**Step 6: Commit**
+
+```bash
+git add src/rules/graph.rs src/unit_tests/rules/graph.rs
+git commit -m "feat: auto-generate natural variant reduction edges in JSON export"
+```
+
+---
+
+### Task 5: Update Existing Tests and Regenerate Graph
+
+**Files:**
+- Modify: `src/unit_tests/rules/graph.rs` (fix any broken count assertions)
+- Regenerate: `docs/book/reductions/reduction_graph.json`
+
+**Step 1: Run full test suite and fix any failures**
+
+Run: `cargo test`
+
+Likely fixes:
+- `test_to_json`: node count assertion `json.nodes.len() >= 10` may need updating
+- `test_to_json`: edge count assertion `json.edges.len() >= 10` may need updating
+- `test_graph_hierarchy_built`: remove assertion about `UnitDiskGraph` having `PlanarGraph` as supertype (if present)
+- `test_is_graph_subtype_direct`: remove `UnitDiskGraph, PlanarGraph` assertion
+- `test_is_graph_subtype_transitive`: update comment and assertions
+- `test_subtype_entries_registered`: count assertion `entries.len() >= 4` may need updating
+
+**Step 2: Regenerate reduction graph JSON**
+
+Run: `cargo run --example export_graph`
+
+**Step 3: Verify the generated JSON contains natural edges**
+
+Run: `cargo test`
+Expected: PASS
+
+**Step 4: Commit**
+
+```bash
+git add src/unit_tests/rules/graph.rs docs/book/reductions/reduction_graph.json
+git commit -m "fix: update tests for corrected hierarchy, regenerate reduction graph"
+```
+
+---
+
+### Task 6: Run clippy and full verification
+
+**Files:** None (verification only)
+
+**Step 1: Run clippy**
+
+Run: `cargo clippy -- -D warnings`
+Expected: PASS
+
+**Step 2: Run full test suite**
+
+Run: `cargo test`
+Expected: PASS
+
+**Step 3: Run fmt check**
+
+Run: `cargo fmt --check`
+Expected: PASS (or fix formatting)
diff --git a/docs/plans/2026-02-13-reduction-macro-redesign-design.md b/docs/plans/2026-02-13-reduction-macro-redesign-design.md
new file mode 100644
index 000000000..467bb991c
--- /dev/null
+++ b/docs/plans/2026-02-13-reduction-macro-redesign-design.md
@@ -0,0 +1,164 @@
+# Reduction Macro Redesign: Dynamic Variant Extraction
+
+## Problem
+
+The `#[reduction]` proc macro infers variant information (graph type, weight type) from type parameters using heuristic pattern matching. This breaks when problem types don't fit the expected `Problem<G, W>` pattern:
+
+- `KColoring<K, G>` — const generic `K` parsed as type param, `SimpleGraph` misidentified as weight
+- `CircuitSAT` — no type params, defaults work but are fragile
+- `KSatisfiability<K>` — literal `3` works, generic `K` doesn't
+- `QUBO<W>` — single-param, needs special-case detection
+
+The macro duplicates logic that `Problem::variant()` already handles correctly, leading to bugs and maintenance burden.
+
+## Solution: Dynamic Function Pointers
+
+Replace static variant inference with function pointers that call `Problem::variant()` at runtime.
+
+### Changes
+
+#### 1. `ReductionEntry` (src/rules/registry.rs)
+
+Replace static `&'static [(&'static str, &'static str)]` fields with `fn()` pointers:
+
+```rust
+// Before:
+pub struct ReductionEntry {
+    pub source_name: &'static str,
+    pub target_name: &'static str,
+    pub source_variant: &'static [(&'static str, &'static str)],
+    pub target_variant: &'static [(&'static str, &'static str)],
+    pub overhead_fn: fn() -> ReductionOverhead,
+    pub module_path: &'static str,
+}
+
+// After:
+pub struct ReductionEntry {
+    pub source_name: &'static str,
+    pub target_name: &'static str,
+    pub source_variant_fn: fn() -> Vec<(&'static str, &'static str)>,
+    pub target_variant_fn: fn() -> Vec<(&'static str, &'static str)>,
+    pub overhead_fn: fn() -> ReductionOverhead,
+    pub module_path: &'static str,
+}
+```
+
+#### 2. Macro Simplification (problemreductions-macros/src/lib.rs)
+
+**Remove** (~120 lines):
+- `extract_graph_type()` — graph type inference from first param
+- `extract_weight_type()` — weight type inference from second param
+- `is_weight_type()` — weight type name list
+- `get_weight_name()` — type-to-name conversion
+- `source_graph`, `target_graph`, `source_weighted`, `target_weighted` attributes from `ReductionAttrs`
+
+**Keep**:
+- `extract_type_name()` — still needed for `source_name`/`target_name`
+- `extract_target_from_trait()` — still needed to get target type from `ReduceTo<T>`
+- `overhead` attribute parsing
+- `ReductionAttrs` (with only `overhead` remaining)
+
+**Add**:
+- Const generic detection: scan impl generics for `const K: usize` patterns
+- Type substitution: replace const generic idents with `usize::MAX` in variant_fn calls
+
+**Generated code**:
+
+For `impl ReduceTo<QUBO<f64>> for MaximumIndependentSet<SimpleGraph, i32>`:
+```rust
+inventory::submit! {
+    crate::rules::registry::ReductionEntry {
+        source_name: "MaximumIndependentSet",
+        target_name: "QUBO",
+        source_variant_fn: || <MaximumIndependentSet<SimpleGraph, i32> as Problem>::variant(),
+        target_variant_fn: || <QUBO<f64> as Problem>::variant(),
+        overhead_fn: || { /* overhead */ },
+        module_path: module_path!(),
+    }
+}
+```
+
+For `impl<const K: usize> ReduceTo<QUBO<f64>> for KColoring<K, SimpleGraph>`:
+```rust
+// K is detected as const generic → substituted with usize::MAX
+// const_usize_str::<{usize::MAX}>() returns "N" → variant becomes ("k", "N")
+inventory::submit! {
+    crate::rules::registry::ReductionEntry {
+        source_name: "KColoring",
+        target_name: "QUBO",
+        source_variant_fn: || <KColoring<{usize::MAX}, SimpleGraph> as crate::traits::Problem>::variant(),
+        target_variant_fn: || <QUBO<f64> as crate::traits::Problem>::variant(),
+        overhead_fn: || { /* overhead */ },
+        module_path: module_path!(),
+    }
+}
+```
+
+For `impl<W: NumericSize> ReduceTo<MinimumVertexCover<SimpleGraph, W>> for MaximumIndependentSet<SimpleGraph, W>`:
+```rust
+// W is a type generic (not const) → use Unweighted as default representative
+inventory::submit! {
+    crate::rules::registry::ReductionEntry {
+        source_name: "MaximumIndependentSet",
+        target_name: "MinimumVertexCover",
+        source_variant_fn: || <MaximumIndependentSet<SimpleGraph, Unweighted> as crate::traits::Problem>::variant(),
+        target_variant_fn: || <MinimumVertexCover<SimpleGraph, Unweighted> as crate::traits::Problem>::variant(),
+        overhead_fn: || { /* overhead */ },
+        module_path: module_path!(),
+    }
+}
+```
+
+#### 3. Manual `inventory::submit!` Updates
+
+Update all 3 manual registrations to use new field names:
+- `coloring_ilp.rs` — may become auto-generated (macro can now handle `KColoring<K, G>`)
+- `factoring_ilp.rs` — keep manual (complex overhead logic)
+- `sat_ksat.rs` — may become auto-generated
+
+#### 4. Graph Builder Updates
+
+Update `export_graph` example and any code that reads `ReductionEntry` to call the fn pointers:
+```rust
+// Before:
+let source_variant = entry.source_variant;
+
+// After:
+let source_variant = (entry.source_variant_fn)();
+```
+
+### Const Generic Substitution Rules
+
+When the macro encounters `impl<const K: usize>`, it:
+1. Identifies `K` as a const generic parameter
+2. In the generated variant_fn calls, replaces `K` with `usize::MAX`
+3. `const_usize_str::<{usize::MAX}>()` → `"N"`, meaning "any K"
+
+When the impl uses a literal const (e.g., `KSatisfiability<2>`):
+- No substitution needed — `2` is already concrete
+- `const_usize_str::<2>()` → `"2"`, preserving the specific value
+
+### Type Generic Substitution Rules
+
+When the macro encounters `impl<W: SomeBound>`, it:
+1. Identifies `W` as a type generic parameter
+2. In the generated variant_fn calls, replaces `W` with `Unweighted`
+3. `Unweighted::variant_name()` or `short_type_name::<Unweighted>()` → `"Unweighted"`
+
+This produces the "base" variant for generic reductions.
+
+### What Doesn't Change
+
+- `Problem::variant()` signature and implementations
+- `ReduceTo` and `ReductionResult` traits
+- Existing reduction rule logic (only the generated registration code changes)
+- `variant()` implementations on all problem types
+- `const_usize_str()` utility
+
+### Benefits
+
+- **Zero inference bugs** — variant info comes from the authoritative `Problem::variant()` implementation
+- **Simpler macro** — ~120 lines of inference logic removed
+- **No new traits** — no `VariantName` trait, no changes to `Problem`
+- **Handles all type patterns** — `<G, W>`, `<K, G>`, `<W>`, no params, all work
+- **Const generics handled automatically** — `usize::MAX` sentinel produces `"N"`
diff --git a/docs/src/reductions/problem_schemas.json b/docs/src/reductions/problem_schemas.json
index 2bb955601..0e89b6f7f 100644
--- a/docs/src/reductions/problem_schemas.json
+++ b/docs/src/reductions/problem_schemas.json
@@ -64,11 +64,6 @@
         "name": "variables",
         "type_name": "Vec<String>",
         "description": "Circuit variable names"
-      },
-      {
-        "name": "weights",
-        "type_name": "Vec<W>",
-        "description": "Assignment weights"
       }
     ]
   },
diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json
index 474335711..d3122dce9 100644
--- a/docs/src/reductions/reduction_graph.json
+++ b/docs/src/reductions/reduction_graph.json
@@ -9,17 +9,7 @@
     {
       "name": "CircuitSAT",
       "variant": {
-        "graph": "SimpleGraph",
-        "weight": "Unweighted"
-      },
-      "category": "satisfiability",
-      "doc_path": "models/specialized/struct.CircuitSAT.html"
-    },
-    {
-      "name": "CircuitSAT",
-      "variant": {
-        "graph": "SimpleGraph",
-        "weight": "i32"
+        "graph": "SimpleGraph"
       },
       "category": "satisfiability",
       "doc_path": "models/specialized/struct.CircuitSAT.html"
@@ -34,7 +24,7 @@
       "name": "Factoring",
       "variant": {
         "graph": "SimpleGraph",
-        "weight": "Unweighted"
+        "weight": "i32"
       },
       "category": "specialized",
       "doc_path": "models/specialized/struct.Factoring.html"
@@ -49,7 +39,7 @@
       "name": "ILP",
       "variant": {
         "graph": "SimpleGraph",
-        "weight": "Unweighted"
+        "weight": "f64"
       },
       "category": "optimization",
       "doc_path": "models/optimization/struct.ILP.html"
@@ -64,8 +54,7 @@
       "name": "KColoring",
       "variant": {
         "graph": "SimpleGraph",
-        "k": "N",
-        "weight": "i32"
+        "k": "3"
       },
       "category": "graph",
       "doc_path": "models/graph/struct.KColoring.html"
@@ -74,30 +63,39 @@
       "name": "KColoring",
       "variant": {
         "graph": "SimpleGraph",
-        "weight": "Unweighted"
+        "k": "N"
       },
       "category": "graph",
       "doc_path": "models/graph/struct.KColoring.html"
     },
     {
-      "name": "KColoring",
+      "name": "KSatisfiability",
+      "variant": {},
+      "category": "satisfiability",
+      "doc_path": "models/satisfiability/struct.KSatisfiability.html"
+    },
+    {
+      "name": "KSatisfiability",
       "variant": {
-        "graph": "SimpleGraph",
-        "weight": "i32"
+        "k": "2",
+        "weight": "Unweighted"
       },
-      "category": "graph",
-      "doc_path": "models/graph/struct.KColoring.html"
+      "category": "satisfiability",
+      "doc_path": "models/satisfiability/struct.KSatisfiability.html"
     },
     {
       "name": "KSatisfiability",
-      "variant": {},
+      "variant": {
+        "k": "3",
+        "weight": "Unweighted"
+      },
       "category": "satisfiability",
       "doc_path": "models/satisfiability/struct.KSatisfiability.html"
     },
     {
       "name": "KSatisfiability",
       "variant": {
-        "graph": "SimpleGraph",
+        "k": "N",
         "weight": "Unweighted"
       },
       "category": "satisfiability",
@@ -324,6 +322,15 @@
       },
       "category": "optimization",
       "doc_path": "models/optimization/struct.SpinGlass.html"
+    },
+    {
+      "name": "SpinGlass",
+      "variant": {
+        "graph": "SimpleGraph",
+        "weight": "i32"
+      },
+      "category": "optimization",
+      "doc_path": "models/optimization/struct.SpinGlass.html"
     }
   ],
   "edges": [
@@ -331,38 +338,14 @@
       "source": {
         "name": "CircuitSAT",
         "variant": {
-          "graph": "SimpleGraph",
-          "weight": "Unweighted"
-        }
-      },
-      "target": {
-        "name": "CircuitSAT",
-        "variant": {
-          "graph": "SimpleGraph",
-          "weight": "i32"
-        }
-      },
-      "overhead": [
-        {
-          "field": "num_gates",
-          "formula": "num_gates"
-        }
-      ],
-      "doc_path": ""
-    },
-    {
-      "source": {
-        "name": "CircuitSAT",
-        "variant": {
-          "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "graph": "SimpleGraph"
         }
       },
       "target": {
         "name": "SpinGlass",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "i32"
         }
       },
       "overhead": [
@@ -382,14 +365,13 @@
         "name": "Factoring",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "i32"
         }
       },
       "target": {
         "name": "CircuitSAT",
         "variant": {
-          "graph": "SimpleGraph",
-          "weight": "i32"
+          "graph": "SimpleGraph"
         }
       },
       "overhead": [
@@ -405,14 +387,14 @@
         "name": "Factoring",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "i32"
         }
       },
       "target": {
         "name": "ILP",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "f64"
         }
       },
       "overhead": [
@@ -432,7 +414,7 @@
         "name": "ILP",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "f64"
         }
       },
       "target": {
@@ -455,15 +437,41 @@
         "name": "KColoring",
         "variant": {
           "graph": "SimpleGraph",
-          "k": "N",
-          "weight": "i32"
+          "k": "3"
+        }
+      },
+      "target": {
+        "name": "KColoring",
+        "variant": {
+          "graph": "SimpleGraph",
+          "k": "N"
+        }
+      },
+      "overhead": [
+        {
+          "field": "num_vertices",
+          "formula": "num_vertices"
+        },
+        {
+          "field": "num_colors",
+          "formula": "num_colors"
+        }
+      ],
+      "doc_path": ""
+    },
+    {
+      "source": {
+        "name": "KColoring",
+        "variant": {
+          "graph": "SimpleGraph",
+          "k": "N"
         }
       },
       "target": {
         "name": "ILP",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "weight": "f64"
         }
       },
       "overhead": [
@@ -483,33 +491,56 @@
         "name": "KColoring",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "Unweighted"
+          "k": "N"
         }
       },
       "target": {
-        "name": "KColoring",
+        "name": "QUBO",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "i32"
+          "weight": "f64"
         }
       },
       "overhead": [
         {
-          "field": "num_vertices",
-          "formula": "num_vertices"
+          "field": "num_vars",
+          "formula": "num_vertices * num_colors"
+        }
+      ],
+      "doc_path": "rules/coloring_qubo/index.html"
+    },
+    {
+      "source": {
+        "name": "KSatisfiability",
+        "variant": {
+          "k": "2",
+          "weight": "Unweighted"
+        }
+      },
+      "target": {
+        "name": "KSatisfiability",
+        "variant": {
+          "k": "N",
+          "weight": "Unweighted"
+        }
+      },
+      "overhead": [
+        {
+          "field": "num_clauses",
+          "formula": "num_clauses"
         },
         {
-          "field": "num_colors",
-          "formula": "num_colors"
+          "field": "num_vars",
+          "formula": "num_vars"
         }
       ],
       "doc_path": ""
     },
     {
       "source": {
-        "name": "KColoring",
+        "name": "KSatisfiability",
         "variant": {
-          "graph": "SimpleGraph",
+          "k": "2",
           "weight": "Unweighted"
         }
       },
@@ -523,16 +554,43 @@
       "overhead": [
         {
           "field": "num_vars",
-          "formula": "num_vertices * num_colors"
+          "formula": "num_vars"
         }
       ],
-      "doc_path": "rules/coloring_qubo/index.html"
+      "doc_path": "rules/ksatisfiability_qubo/index.html"
     },
     {
       "source": {
         "name": "KSatisfiability",
         "variant": {
-          "graph": "SimpleGraph",
+          "k": "3",
+          "weight": "Unweighted"
+        }
+      },
+      "target": {
+        "name": "KSatisfiability",
+        "variant": {
+          "k": "N",
+          "weight": "Unweighted"
+        }
+      },
+      "overhead": [
+        {
+          "field": "num_clauses",
+          "formula": "num_clauses"
+        },
+        {
+          "field": "num_vars",
+          "formula": "num_vars"
+        }
+      ],
+      "doc_path": ""
+    },
+    {
+      "source": {
+        "name": "KSatisfiability",
+        "variant": {
+          "k": "3",
           "weight": "Unweighted"
         }
       },
@@ -555,7 +613,7 @@
       "source": {
         "name": "KSatisfiability",
         "variant": {
-          "graph": "SimpleGraph",
+          "k": "N",
           "weight": "Unweighted"
         }
       },
@@ -1114,7 +1172,7 @@
         "name": "KColoring",
         "variant": {
           "graph": "SimpleGraph",
-          "weight": "i32"
+          "k": "3"
         }
       },
       "overhead": [
@@ -1140,7 +1198,7 @@
       "target": {
         "name": "KSatisfiability",
         "variant": {
-          "graph": "SimpleGraph",
+          "k": "N",
           "weight": "Unweighted"
         }
       },
@@ -1291,6 +1349,33 @@
       ],
       "doc_path": ""
     },
+    {
+      "source": {
+        "name": "SpinGlass",
+        "variant": {
+          "graph": "SimpleGraph",
+          "weight": "Unweighted"
+        }
+      },
+      "target": {
+        "name": "SpinGlass",
+        "variant": {
+          "graph": "SimpleGraph",
+          "weight": "i32"
+        }
+      },
+      "overhead": [
+        {
+          "field": "num_spins",
+          "formula": "num_spins"
+        },
+        {
+          "field": "num_interactions",
+          "formula": "num_interactions"
+        }
+      ],
+      "doc_path": ""
+    },
     {
       "source": {
         "name": "SpinGlass",
@@ -1313,6 +1398,33 @@
         }
       ],
       "doc_path": "rules/spinglass_qubo/index.html"
+    },
+    {
+      "source": {
+        "name": "SpinGlass",
+        "variant": {
+          "graph": "SimpleGraph",
+          "weight": "i32"
+        }
+      },
+      "target": {
+        "name": "SpinGlass",
+        "variant": {
+          "graph": "SimpleGraph",
+          "weight": "f64"
+        }
+      },
+      "overhead": [
+        {
+          "field": "num_spins",
+          "formula": "num_spins"
+        },
+        {
+          "field": "num_interactions",
+          "formula": "num_interactions"
+        }
+      ],
+      "doc_path": ""
     }
   ]
 }
\ No newline at end of file
diff --git a/examples/reduction_circuitsat_to_spinglass.rs b/examples/reduction_circuitsat_to_spinglass.rs
index c35c22ede..f7c7f940d 100644
--- a/examples/reduction_circuitsat_to_spinglass.rs
+++ b/examples/reduction_circuitsat_to_spinglass.rs
@@ -52,7 +52,7 @@ pub fn run() {
             BooleanExpr::or(vec![BooleanExpr::var("ab"), BooleanExpr::var("cin_t")]),
         ),
     ]);
-    let circuit_sat = CircuitSAT::<i32>::new(circuit);
+    let circuit_sat = CircuitSAT::new(circuit);
 
     println!("=== Circuit-SAT to Spin Glass Reduction ===\n");
     println!("Source circuit: 1-bit full adder (a, b, cin -> sum, cout)");
@@ -136,8 +136,8 @@ pub fn run() {
 
     let data = ReductionData {
         source: ProblemSide {
-            problem: CircuitSAT::<i32>::NAME.to_string(),
-            variant: variant_to_map(CircuitSAT::<i32>::variant()),
+            problem: CircuitSAT::NAME.to_string(),
+            variant: variant_to_map(CircuitSAT::variant()),
             instance: serde_json::json!({
                 "num_gates": circuit_sat.circuit().num_assignments(),
                 "num_variables": circuit_sat.num_variables(),
diff --git a/examples/reduction_factoring_to_circuitsat.rs b/examples/reduction_factoring_to_circuitsat.rs
index 114b1023e..e281c55ad 100644
--- a/examples/reduction_factoring_to_circuitsat.rs
+++ b/examples/reduction_factoring_to_circuitsat.rs
@@ -68,7 +68,7 @@ pub fn run() {
     }
 
     // 3. Reduce Factoring -> CircuitSAT
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
     let circuit_sat = reduction.target_problem();
 
     println!("\n=== Factoring -> CircuitSAT ===");
@@ -204,8 +204,8 @@ pub fn run() {
             }),
         },
         target: ProblemSide {
-            problem: CircuitSAT::<i32>::NAME.to_string(),
-            variant: variant_to_map(CircuitSAT::<i32>::variant()),
+            problem: CircuitSAT::NAME.to_string(),
+            variant: variant_to_map(CircuitSAT::variant()),
             instance: serde_json::json!({
                 "num_variables": circuit_sat.num_variables(),
                 "num_gates": circuit_sat.circuit().num_assignments(),
diff --git a/examples/reduction_kcoloring_to_ilp.rs b/examples/reduction_kcoloring_to_ilp.rs
index 0a6f1071d..01130477e 100644
--- a/examples/reduction_kcoloring_to_ilp.rs
+++ b/examples/reduction_kcoloring_to_ilp.rs
@@ -24,7 +24,7 @@ use problemreductions::topology::SimpleGraph;
 pub fn run() {
     // 1. Create KColoring instance: Petersen graph (10 vertices, 15 edges) with 3 colors, χ=3
     let (num_vertices, edges) = petersen();
-    let coloring = KColoring::<3, SimpleGraph, i32>::new(num_vertices, edges.clone());
+    let coloring = KColoring::<3, SimpleGraph>::new(num_vertices, edges.clone());
 
     // 2. Reduce to ILP
     let reduction = ReduceTo::<ILP>::reduce_to(&coloring);
@@ -75,8 +75,8 @@ pub fn run() {
 
     let data = ReductionData {
         source: ProblemSide {
-            problem: KColoring::<3, SimpleGraph, i32>::NAME.to_string(),
-            variant: variant_to_map(KColoring::<3, SimpleGraph, i32>::variant()),
+            problem: KColoring::<3, SimpleGraph>::NAME.to_string(),
+            variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()),
             instance: serde_json::json!({
                 "num_vertices": coloring.num_vertices(),
                 "num_edges": coloring.num_edges(),
diff --git a/examples/reduction_kcoloring_to_qubo.rs b/examples/reduction_kcoloring_to_qubo.rs
index f960fb97e..b9a7b7cab 100644
--- a/examples/reduction_kcoloring_to_qubo.rs
+++ b/examples/reduction_kcoloring_to_qubo.rs
@@ -38,7 +38,7 @@ pub fn run() {
 
     // House graph: 5 vertices, 6 edges (square base + triangle roof), χ=3
     let (num_vertices, edges) = house();
-    let kc = KColoring::<3, SimpleGraph, i32>::new(num_vertices, edges.clone());
+    let kc = KColoring::<3, SimpleGraph>::new(num_vertices, edges.clone());
 
     // Reduce to QUBO
     let reduction = ReduceTo::<QUBO>::reduce_to(&kc);
@@ -93,8 +93,8 @@ pub fn run() {
 
     let data = ReductionData {
         source: ProblemSide {
-            problem: KColoring::<3, SimpleGraph, i32>::NAME.to_string(),
-            variant: variant_to_map(KColoring::<3, SimpleGraph, i32>::variant()),
+            problem: KColoring::<3, SimpleGraph>::NAME.to_string(),
+            variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()),
             instance: serde_json::json!({
                 "num_vertices": kc.num_vertices(),
                 "num_edges": kc.num_edges(),
diff --git a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs
index 486d0f9e4..048c5c8b4 100644
--- a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs
+++ b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs
@@ -56,7 +56,12 @@ pub fn run() {
 
         println!(
             "  Solution {}: target={:?} (size={:?}), source={:?} (size={:?}, valid={})",
-            i, target_sol, target_size, source_sol, source_size, source_size.is_valid()
+            i,
+            target_sol,
+            target_size,
+            source_sol,
+            source_size,
+            source_size.is_valid()
         );
 
         assert!(
diff --git a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs
index 5b0c60132..b8dff8144 100644
--- a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs
+++ b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs
@@ -58,7 +58,12 @@ pub fn run() {
         // Both are minimization problems, infeasible configs return Invalid
         println!(
             "  Solution {}: target={:?} (size={:?}), source={:?} (size={:?}, valid={})",
-            i, target_sol, target_size, source_sol, source_size, source_size.is_valid()
+            i,
+            target_sol,
+            target_size,
+            source_sol,
+            source_size,
+            source_size.is_valid()
         );
 
         assert!(
diff --git a/examples/reduction_satisfiability_to_kcoloring.rs b/examples/reduction_satisfiability_to_kcoloring.rs
index d2986a577..e19e92156 100644
--- a/examples/reduction_satisfiability_to_kcoloring.rs
+++ b/examples/reduction_satisfiability_to_kcoloring.rs
@@ -44,8 +44,8 @@ pub fn run() {
     println!("  (Unit clauses avoid OR-gadgets, keeping vertex count manageable for BruteForce)");
 
     // 2. Reduce to 3-Coloring
-    //    SAT reduces to KColoring<3, SimpleGraph, i32>
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    //    SAT reduces to KColoring<3, SimpleGraph>
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     println!("\n=== Problem Transformation ===");
@@ -66,8 +66,7 @@ pub fn run() {
     let solver = BruteForce::new();
     // Find all satisfying 3-colorings by iterating through configs
     let dims = coloring.dims();
-    let all_configs: Vec<Vec<usize>> =
-        problemreductions::config::DimsIterator::new(dims).collect();
+    let all_configs: Vec<Vec<usize>> = problemreductions::config::DimsIterator::new(dims).collect();
     let coloring_solutions: Vec<&[usize]> = all_configs
         .iter()
         .filter(|config| coloring.evaluate(config))
@@ -129,8 +128,8 @@ pub fn run() {
             }),
         },
         target: ProblemSide {
-            problem: KColoring::<3, SimpleGraph, i32>::NAME.to_string(),
-            variant: variant_to_map(KColoring::<3, SimpleGraph, i32>::variant()),
+            problem: KColoring::<3, SimpleGraph>::NAME.to_string(),
+            variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()),
             instance: serde_json::json!({
                 "num_vertices": coloring.num_vertices(),
                 "num_edges": coloring.num_edges(),
diff --git a/problemreductions-macros/src/lib.rs b/problemreductions-macros/src/lib.rs
index 923ba3c0a..e1396075c 100644
--- a/problemreductions-macros/src/lib.rs
+++ b/problemreductions-macros/src/lib.rs
@@ -6,35 +6,29 @@
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::quote;
+use std::collections::HashSet;
 use syn::{parse_macro_input, GenericArgument, ItemImpl, Path, PathArguments, Type};
 
 /// Attribute macro for automatic reduction registration.
 ///
-/// This macro parses a `ReduceTo` impl block and automatically generates
-/// the corresponding `inventory::submit!` call with the correct metadata.
+/// Parses a `ReduceTo` impl block and generates the corresponding `inventory::submit!`
+/// call. Variant fields are derived from `Problem::variant()` when possible.
 ///
-/// # Type Parameter Convention
+/// # Variant Derivation
 ///
-/// The macro extracts graph and weight type information from type parameters:
-/// - `Problem<G>` where `G` is a graph type - extracts graph type name
-/// - `Problem<G, W>` where `W` is a weight type - weighted if W != Unweighted
+/// - **Types without type generics** (e.g., `KColoring<K, SimpleGraph>`): calls
+///   `Problem::variant()` at runtime. Const generics like `K` are substituted with
+///   `usize::MAX` (maps to `"N"` via `const_usize_str`).
+/// - **Types with type generics** (e.g., `MaxCut<SimpleGraph, W>`): falls back to
+///   constructing `("graph", ...), ("weight", ...)` from type parameter analysis.
 ///
-/// # Example
+/// # Attributes
 ///
-/// ```ignore
-/// #[reduction(
-///     source_graph = "SimpleGraph",
-///     target_graph = "GridGraph",
-///     source_weighted = false,
-///     target_weighted = true,
-/// )]
-/// impl ReduceTo<IndependentSet<i32, GridGraph>> for IndependentSet<Unweighted, SimpleGraph> {
-///     type Result = ReductionISToGridIS;
-///     fn reduce_to(&self) -> Self::Result { ... }
-/// }
-/// ```
-///
-/// The macro also supports inferring from type names when explicit attributes aren't provided.
+/// - `overhead = { expr }` — overhead specification (required for non-trivial reductions)
+/// - `source_graph = "..."` — override source graph type (fallback path only)
+/// - `target_graph = "..."` — override target graph type (fallback path only)
+/// - `source_weighted = bool` — override source weight (fallback path only)
+/// - `target_weighted = bool` — override target weight (fallback path only)
 #[proc_macro_attribute]
 pub fn reduction(attr: TokenStream, item: TokenStream) -> TokenStream {
     let attrs = parse_macro_input!(attr as ReductionAttrs);
@@ -119,13 +113,228 @@ fn extract_type_name(ty: &Type) -> Option<String> {
     }
 }
 
+/// Collect const generic parameter names from impl generics.
+/// e.g., `impl<const K: usize>` → {"K"}
+fn collect_const_generic_names(generics: &syn::Generics) -> HashSet<String> {
+    generics
+        .params
+        .iter()
+        .filter_map(|p| {
+            if let syn::GenericParam::Const(c) = p {
+                Some(c.ident.to_string())
+            } else {
+                None
+            }
+        })
+        .collect()
+}
+
+/// Collect type generic parameter names from impl generics.
+/// e.g., `impl<G: Graph, W: NumericSize>` → {"G", "W"}
+fn collect_type_generic_names(generics: &syn::Generics) -> HashSet<String> {
+    generics
+        .params
+        .iter()
+        .filter_map(|p| {
+            if let syn::GenericParam::Type(t) = p {
+                Some(t.ident.to_string())
+            } else {
+                None
+            }
+        })
+        .collect()
+}
+
+/// Check if a type uses any of the given type generic parameters.
+fn type_uses_type_generics(ty: &Type, type_generics: &HashSet<String>) -> bool {
+    match ty {
+        Type::Path(type_path) => {
+            if let Some(segment) = type_path.path.segments.last() {
+                if let PathArguments::AngleBracketed(args) = &segment.arguments {
+                    for arg in args.args.iter() {
+                        if let GenericArgument::Type(Type::Path(inner)) = arg {
+                            if let Some(ident) = inner.path.get_ident() {
+                                if type_generics.contains(&ident.to_string()) {
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            false
+        }
+        _ => false,
+    }
+}
+
+/// Rewrite a type by substituting const generic names with `{usize::MAX}`.
+///
+/// e.g., `KColoring<K, SimpleGraph>` with const_generics={"K"}
+/// → `KColoring<{usize::MAX}, SimpleGraph>`
+fn rewrite_const_generics(ty: &Type, const_generics: &HashSet<String>) -> Type {
+    match ty {
+        Type::Path(type_path) => {
+            let mut new_path = type_path.clone();
+            if let Some(segment) = new_path.path.segments.last_mut() {
+                if let PathArguments::AngleBracketed(args) = &mut segment.arguments {
+                    for arg in args.args.iter_mut() {
+                        if let GenericArgument::Type(Type::Path(inner)) = arg {
+                            if let Some(ident) = inner.path.get_ident() {
+                                if const_generics.contains(&ident.to_string()) {
+                                    // Replace const generic with sentinel value
+                                    *arg = syn::parse_quote!({ usize::MAX });
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Type::Path(new_path)
+        }
+        _ => ty.clone(),
+    }
+}
+
+/// Generate the variant fn body for a type.
+///
+/// If the type has no type generics: calls `Problem::variant()` with const generic
+/// sentinels. Otherwise falls back to manual `("graph", ...), ("weight", ...)` construction.
+fn make_variant_fn_body(
+    ty: &Type,
+    const_generics: &HashSet<String>,
+    type_generics: &HashSet<String>,
+    graph_override: Option<&str>,
+    weighted_override: Option<bool>,
+) -> TokenStream2 {
+    if type_uses_type_generics(ty, type_generics) {
+        // Fallback: construct variant manually from type parameter analysis
+        let graph = graph_override
+            .map(|s| s.to_string())
+            .or_else(|| extract_graph_type(ty))
+            .unwrap_or_else(|| "SimpleGraph".to_string());
+        let weight = weighted_override
+            .map(|w| {
+                if w {
+                    "i32".to_string()
+                } else {
+                    "Unweighted".to_string()
+                }
+            })
+            .unwrap_or_else(|| {
+                extract_weight_type(ty)
+                    .map(|t| get_weight_name(&t))
+                    .unwrap_or_else(|| "Unweighted".to_string())
+            });
+        quote! { vec![("graph", #graph), ("weight", #weight)] }
+    } else {
+        // Call Problem::variant() with const generic sentinels
+        let rewritten = rewrite_const_generics(ty, const_generics);
+        quote! { <#rewritten as crate::traits::Problem>::variant() }
+    }
+}
+
+/// Generate the reduction entry code
+fn generate_reduction_entry(
+    attrs: &ReductionAttrs,
+    impl_block: &ItemImpl,
+) -> syn::Result<TokenStream2> {
+    // Extract the trait path (should be ReduceTo<Target>)
+    let trait_path = impl_block
+        .trait_
+        .as_ref()
+        .map(|(_, path, _)| path)
+        .ok_or_else(|| syn::Error::new_spanned(impl_block, "Expected impl ReduceTo<T> for S"))?;
+
+    // Extract target type from ReduceTo<Target>
+    let target_type = extract_target_from_trait(trait_path)?;
+
+    // Extract source type (Self type)
+    let source_type = &impl_block.self_ty;
+
+    // Get type names
+    let source_name = extract_type_name(source_type)
+        .ok_or_else(|| syn::Error::new_spanned(source_type, "Cannot extract source type name"))?;
+    let target_name = extract_type_name(&target_type)
+        .ok_or_else(|| syn::Error::new_spanned(&target_type, "Cannot extract target type name"))?;
+
+    // Collect generic parameter info from the impl block
+    let const_generics = collect_const_generic_names(&impl_block.generics);
+    let type_generics = collect_type_generic_names(&impl_block.generics);
+
+    // Generate variant fn bodies
+    let source_variant_body = make_variant_fn_body(
+        source_type,
+        &const_generics,
+        &type_generics,
+        attrs.source_graph.as_deref(),
+        attrs.source_weighted,
+    );
+    let target_variant_body = make_variant_fn_body(
+        &target_type,
+        &const_generics,
+        &type_generics,
+        attrs.target_graph.as_deref(),
+        attrs.target_weighted,
+    );
+
+    // Generate overhead or use default
+    let overhead = attrs.overhead.clone().unwrap_or_else(|| {
+        quote! {
+            crate::rules::registry::ReductionOverhead::default()
+        }
+    });
+
+    // Generate the combined output
+    let output = quote! {
+        #impl_block
+
+        inventory::submit! {
+            crate::rules::registry::ReductionEntry {
+                source_name: #source_name,
+                target_name: #target_name,
+                source_variant_fn: || { #source_variant_body },
+                target_variant_fn: || { #target_variant_body },
+                overhead_fn: || { #overhead },
+                module_path: module_path!(),
+            }
+        }
+    };
+
+    Ok(output)
+}
+
+/// Extract the target type from ReduceTo<Target> trait path
+fn extract_target_from_trait(path: &Path) -> syn::Result<Type> {
+    let segment = path
+        .segments
+        .last()
+        .ok_or_else(|| syn::Error::new_spanned(path, "Empty trait path"))?;
+
+    if segment.ident != "ReduceTo" {
+        return Err(syn::Error::new_spanned(segment, "Expected ReduceTo trait"));
+    }
+
+    if let PathArguments::AngleBracketed(args) = &segment.arguments {
+        if let Some(GenericArgument::Type(ty)) = args.args.first() {
+            return Ok(ty.clone());
+        }
+    }
+
+    Err(syn::Error::new_spanned(
+        segment,
+        "Expected ReduceTo<Target> with type parameter",
+    ))
+}
+
+// --- Fallback helpers for types with type generics ---
+
 /// Extract graph type from type parameters (first parameter in `Problem<G, W>` order)
 fn extract_graph_type(ty: &Type) -> Option<String> {
     match ty {
         Type::Path(type_path) => {
             let segment = type_path.path.segments.last()?;
             if let PathArguments::AngleBracketed(args) = &segment.arguments {
-                // Get the first type argument which is the graph type
                 for arg in args.args.iter() {
                     if let GenericArgument::Type(Type::Path(inner_path)) = arg {
                         let name = inner_path
@@ -141,11 +350,11 @@ fn extract_graph_type(ty: &Type) -> Option<String> {
                                 .map(|c| c.is_ascii_uppercase())
                                 .unwrap_or(false)
                         {
-                            return None; // Generic param, let it default
+                            return None;
                         }
-                        // Skip known weight types - for single-param problems like QUBO<W>
+                        // Skip known weight types
                         if is_weight_type(&name) {
-                            return None; // Weight type in first position, not a graph type
+                            return None;
                         }
                         return Some(name);
                     }
@@ -163,8 +372,6 @@ fn is_weight_type(name: &str) -> bool {
 }
 
 /// Extract weight type from type parameters.
-/// For `Problem<G, W>` (two params): returns W (second param).
-/// For `Problem<W>` (single weight param): returns W (first param).
 fn extract_weight_type(ty: &Type) -> Option<Type> {
     match ty {
         Type::Path(type_path) => {
@@ -184,7 +391,6 @@ fn extract_weight_type(ty: &Type) -> Option<Type> {
 
                 match type_args.len() {
                     1 => {
-                        // Single param - check if it's a weight type
                         let first = type_args[0];
                         if let Type::Path(inner_path) = first {
                             let name = inner_path.path.segments.last()?.ident.to_string();
@@ -195,8 +401,14 @@ fn extract_weight_type(ty: &Type) -> Option<Type> {
                         None
                     }
                     2 => {
-                        // Two params: Problem<G, W> - return second
-                        Some(type_args[1].clone())
+                        let second = type_args[1];
+                        if let Type::Path(inner_path) = second {
+                            let name = inner_path.path.segments.last()?.ident.to_string();
+                            if is_weight_type(&name) {
+                                return Some(second.clone());
+                            }
+                        }
+                        None
                     }
                     _ => None,
                 }
@@ -210,7 +422,7 @@ fn extract_weight_type(ty: &Type) -> Option<Type> {
 
 /// Get weight type name as a string for the variant.
 /// Single-letter uppercase names are treated as generic type parameters
-/// and default to "Unweighted" since they're not concrete types.
+/// and default to "Unweighted".
 fn get_weight_name(ty: &Type) -> String {
     match ty {
         Type::Path(type_path) => {
@@ -220,7 +432,6 @@ fn get_weight_name(ty: &Type) -> String {
                 .last()
                 .map(|s| s.ident.to_string())
                 .unwrap_or_else(|| "Unweighted".to_string());
-            // Treat single uppercase letters as generic params, default to Unweighted
             if name.len() == 1
                 && name
                     .chars()
@@ -236,118 +447,3 @@ fn get_weight_name(ty: &Type) -> String {
         _ => "Unweighted".to_string(),
     }
 }
-
-/// Generate the reduction entry code
-fn generate_reduction_entry(
-    attrs: &ReductionAttrs,
-    impl_block: &ItemImpl,
-) -> syn::Result<TokenStream2> {
-    // Extract the trait path (should be ReduceTo<Target>)
-    let trait_path = impl_block
-        .trait_
-        .as_ref()
-        .map(|(_, path, _)| path)
-        .ok_or_else(|| syn::Error::new_spanned(impl_block, "Expected impl ReduceTo<T> for S"))?;
-
-    // Extract target type from ReduceTo<Target>
-    let target_type = extract_target_from_trait(trait_path)?;
-
-    // Extract source type (Self type)
-    let source_type = &impl_block.self_ty;
-
-    // Get type names
-    let source_name = extract_type_name(source_type)
-        .ok_or_else(|| syn::Error::new_spanned(source_type, "Cannot extract source type name"))?;
-    let target_name = extract_type_name(&target_type)
-        .ok_or_else(|| syn::Error::new_spanned(&target_type, "Cannot extract target type name"))?;
-
-    // Determine weight type names
-    let source_weight_name = attrs
-        .source_weighted
-        .map(|w| {
-            if w {
-                "i32".to_string()
-            } else {
-                "Unweighted".to_string()
-            }
-        })
-        .unwrap_or_else(|| {
-            extract_weight_type(source_type)
-                .map(|t| get_weight_name(&t))
-                .unwrap_or_else(|| "Unweighted".to_string())
-        });
-    let target_weight_name = attrs
-        .target_weighted
-        .map(|w| {
-            if w {
-                "i32".to_string()
-            } else {
-                "Unweighted".to_string()
-            }
-        })
-        .unwrap_or_else(|| {
-            extract_weight_type(&target_type)
-                .map(|t| get_weight_name(&t))
-                .unwrap_or_else(|| "Unweighted".to_string())
-        });
-
-    // Determine graph types
-    let source_graph = attrs
-        .source_graph
-        .clone()
-        .or_else(|| extract_graph_type(source_type))
-        .unwrap_or_else(|| "SimpleGraph".to_string());
-    let target_graph = attrs
-        .target_graph
-        .clone()
-        .or_else(|| extract_graph_type(&target_type))
-        .unwrap_or_else(|| "SimpleGraph".to_string());
-
-    // Generate overhead or use default
-    let overhead = attrs.overhead.clone().unwrap_or_else(|| {
-        quote! {
-            crate::rules::registry::ReductionOverhead::default()
-        }
-    });
-
-    // Generate the combined output
-    let output = quote! {
-        #impl_block
-
-        inventory::submit! {
-            crate::rules::registry::ReductionEntry {
-                source_name: #source_name,
-                target_name: #target_name,
-                source_variant: &[("graph", #source_graph), ("weight", #source_weight_name)],
-                target_variant: &[("graph", #target_graph), ("weight", #target_weight_name)],
-                overhead_fn: || { #overhead },
-                module_path: module_path!(),
-            }
-        }
-    };
-
-    Ok(output)
-}
-
-/// Extract the target type from ReduceTo<Target> trait path
-fn extract_target_from_trait(path: &Path) -> syn::Result<Type> {
-    let segment = path
-        .segments
-        .last()
-        .ok_or_else(|| syn::Error::new_spanned(path, "Empty trait path"))?;
-
-    if segment.ident != "ReduceTo" {
-        return Err(syn::Error::new_spanned(segment, "Expected ReduceTo trait"));
-    }
-
-    if let PathArguments::AngleBracketed(args) = &segment.arguments {
-        if let Some(GenericArgument::Type(ty)) = args.args.first() {
-            return Ok(ty.clone());
-        }
-    }
-
-    Err(syn::Error::new_spanned(
-        segment,
-        "Expected ReduceTo<Target> with type parameter",
-    ))
-}
diff --git a/src/lib.rs b/src/lib.rs
index e307f4877..f71bab44c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -98,7 +98,9 @@ pub mod prelude {
     pub use crate::rules::{ReduceTo, ReductionResult};
     pub use crate::solvers::{BruteForce, Solver};
     pub use crate::traits::{OptimizationProblem, Problem};
-    pub use crate::types::{Direction, NumericSize, NumericWeight, ProblemSize, SolutionSize, Unweighted, Weights};
+    pub use crate::types::{
+        Direction, NumericSize, NumericWeight, ProblemSize, SolutionSize, Unweighted, Weights,
+    };
 }
 
 // Re-export commonly used items at crate root
diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs
index d3c083d81..8550b442a 100644
--- a/src/models/graph/kcoloring.rs
+++ b/src/models/graph/kcoloring.rs
@@ -7,7 +7,6 @@ use crate::registry::{FieldInfo, ProblemSchemaEntry};
 use crate::topology::{Graph, SimpleGraph};
 use crate::traits::Problem;
 use serde::{Deserialize, Serialize};
-use std::marker::PhantomData;
 
 inventory::submit! {
     ProblemSchemaEntry {
@@ -28,7 +27,6 @@ inventory::submit! {
 ///
 /// * `K` - Number of colors (const generic)
 /// * `G` - Graph type (e.g., SimpleGraph, GridGraph)
-/// * `W` - Weight type (typically i32 for unweighted problems)
 ///
 /// # Example
 ///
@@ -38,7 +36,7 @@ inventory::submit! {
 /// use problemreductions::{Problem, Solver, BruteForce};
 ///
 /// // Triangle graph needs at least 3 colors
-/// let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+/// let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
 ///
 /// let solver = BruteForce::new();
 /// let solutions = solver.find_all_satisfying(&problem);
@@ -49,15 +47,12 @@ inventory::submit! {
 /// }
 /// ```
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct KColoring<const K: usize, G, W> {
+pub struct KColoring<const K: usize, G> {
     /// The underlying graph.
     graph: G,
-    /// Phantom data for weight type.
-    #[serde(skip)]
-    _phantom: PhantomData<W>,
 }
 
-impl<const K: usize, W: Clone + Default> KColoring<K, SimpleGraph, W> {
+impl<const K: usize> KColoring<K, SimpleGraph> {
     /// Create a new K-Coloring problem.
     ///
     /// # Arguments
@@ -65,20 +60,14 @@ impl<const K: usize, W: Clone + Default> KColoring<K, SimpleGraph, W> {
     /// * `edges` - List of edges as (u, v) pairs
     pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self {
         let graph = SimpleGraph::new(num_vertices, edges);
-        Self {
-            graph,
-            _phantom: PhantomData,
-        }
+        Self { graph }
     }
 }
 
-impl<const K: usize, G: Graph, W: Clone + Default> KColoring<K, G, W> {
+impl<const K: usize, G: Graph> KColoring<K, G> {
     /// Create a K-Coloring problem from an existing graph.
     pub fn from_graph(graph: G) -> Self {
-        Self {
-            graph,
-            _phantom: PhantomData,
-        }
+        Self { graph }
     }
 
     /// Get a reference to the underlying graph.
@@ -119,10 +108,9 @@ impl<const K: usize, G: Graph, W: Clone + Default> KColoring<K, G, W> {
     }
 }
 
-impl<const K: usize, G, W> Problem for KColoring<K, G, W>
+impl<const K: usize, G> Problem for KColoring<K, G>
 where
     G: Graph,
-    W: Clone + Default + 'static,
 {
     const NAME: &'static str = "KColoring";
     type Metric = bool;
@@ -131,7 +119,6 @@ where
         vec![
             ("k", crate::variant::const_usize_str::<K>()),
             ("graph", crate::variant::short_type_name::<G>()),
-            ("weight", crate::variant::short_type_name::<W>()),
         ]
     }
 
diff --git a/src/models/optimization/ilp.rs b/src/models/optimization/ilp.rs
index 86e2f0750..80de1ea7b 100644
--- a/src/models/optimization/ilp.rs
+++ b/src/models/optimization/ilp.rs
@@ -350,10 +350,7 @@ impl Problem for ILP {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", "f64"),
-        ]
+        vec![("graph", "SimpleGraph"), ("weight", "f64")]
     }
 }
 
diff --git a/src/models/satisfiability/ksat.rs b/src/models/satisfiability/ksat.rs
index f54606c89..26ce049fd 100644
--- a/src/models/satisfiability/ksat.rs
+++ b/src/models/satisfiability/ksat.rs
@@ -150,7 +150,10 @@ impl<const K: usize> Problem for KSatisfiability<K> {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![("k", crate::variant::const_usize_str::<K>()), ("weight", "Unweighted")]
+        vec![
+            ("k", crate::variant::const_usize_str::<K>()),
+            ("weight", "Unweighted"),
+        ]
     }
 }
 
diff --git a/src/models/specialized/biclique_cover.rs b/src/models/specialized/biclique_cover.rs
index b0b53cc4c..3c3053795 100644
--- a/src/models/specialized/biclique_cover.rs
+++ b/src/models/specialized/biclique_cover.rs
@@ -225,10 +225,7 @@ impl Problem for BicliqueCover {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", "i32"),
-        ]
+        vec![("graph", "SimpleGraph"), ("weight", "i32")]
     }
 }
 
diff --git a/src/models/specialized/bmf.rs b/src/models/specialized/bmf.rs
index fc50c8ed5..6eac46ad5 100644
--- a/src/models/specialized/bmf.rs
+++ b/src/models/specialized/bmf.rs
@@ -205,10 +205,7 @@ impl Problem for BMF {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", "i32"),
-        ]
+        vec![("graph", "SimpleGraph"), ("weight", "i32")]
     }
 }
 
diff --git a/src/models/specialized/circuit.rs b/src/models/specialized/circuit.rs
index 338c680a4..f07869126 100644
--- a/src/models/specialized/circuit.rs
+++ b/src/models/specialized/circuit.rs
@@ -15,7 +15,6 @@ inventory::submit! {
         fields: &[
             FieldInfo { name: "circuit", type_name: "Circuit", description: "The boolean circuit" },
             FieldInfo { name: "variables", type_name: "Vec<String>", description: "Circuit variable names" },
-            FieldInfo { name: "weights", type_name: "Vec<W>", description: "Assignment weights" },
         ],
     }
 }
@@ -190,7 +189,7 @@ impl Circuit {
 /// The Circuit SAT problem.
 ///
 /// Given a boolean circuit, find variable assignments that satisfy
-/// as many assignments as possible (or all of them).
+/// all circuit constraints.
 ///
 /// # Example
 ///
@@ -206,7 +205,7 @@ impl Circuit {
 ///     ),
 /// ]);
 ///
-/// let problem = CircuitSAT::<i32>::new(circuit);
+/// let problem = CircuitSAT::new(circuit);
 /// let solver = BruteForce::new();
 /// let solutions = solver.find_all_satisfying(&problem);
 ///
@@ -214,38 +213,18 @@ impl Circuit {
 /// assert!(!solutions.is_empty());
 /// ```
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct CircuitSAT<W = i32> {
+pub struct CircuitSAT {
     /// The circuit.
     circuit: Circuit,
     /// Variables in order.
     variables: Vec<String>,
-    /// Weights for each assignment.
-    weights: Vec<W>,
 }
 
-impl<W: Clone + Default + From<i32>> CircuitSAT<W> {
-    /// Create a new CircuitSAT problem with unit weights.
+impl CircuitSAT {
+    /// Create a new CircuitSAT problem.
     pub fn new(circuit: Circuit) -> Self {
         let variables = circuit.variables();
-        let weights = vec![W::from(1); circuit.num_assignments()];
-        Self {
-            circuit,
-            variables,
-            weights,
-        }
-    }
-}
-
-impl<W> CircuitSAT<W> {
-    /// Create a CircuitSAT problem with custom weights.
-    pub fn with_weights(circuit: Circuit, weights: Vec<W>) -> Self {
-        assert_eq!(weights.len(), circuit.num_assignments());
-        let variables = circuit.variables();
-        Self {
-            circuit,
-            variables,
-            weights,
-        }
+        Self { circuit, variables }
     }
 
     /// Get the circuit.
@@ -286,10 +265,7 @@ pub fn is_circuit_satisfying(circuit: &Circuit, assignments: &HashMap<String, bo
         .all(|a| a.is_satisfied(assignments))
 }
 
-impl<W> Problem for CircuitSAT<W>
-where
-    W: Clone + Default + 'static,
-{
+impl Problem for CircuitSAT {
     const NAME: &'static str = "CircuitSAT";
     type Metric = bool;
 
@@ -302,10 +278,7 @@ where
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", crate::variant::short_type_name::<W>()),
-        ]
+        vec![("graph", "SimpleGraph")]
     }
 }
 
diff --git a/src/models/specialized/factoring.rs b/src/models/specialized/factoring.rs
index d1639afa3..bb831aa0c 100644
--- a/src/models/specialized/factoring.rs
+++ b/src/models/specialized/factoring.rs
@@ -133,10 +133,7 @@ impl Problem for Factoring {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", "i32"),
-        ]
+        vec![("graph", "SimpleGraph"), ("weight", "i32")]
     }
 }
 
diff --git a/src/models/specialized/paintshop.rs b/src/models/specialized/paintshop.rs
index 9e0097025..7a518964e 100644
--- a/src/models/specialized/paintshop.rs
+++ b/src/models/specialized/paintshop.rs
@@ -173,10 +173,7 @@ impl Problem for PaintShop {
     }
 
     fn variant() -> Vec<(&'static str, &'static str)> {
-        vec![
-            ("graph", "SimpleGraph"),
-            ("weight", "i32"),
-        ]
+        vec![("graph", "SimpleGraph"), ("weight", "i32")]
     }
 }
 
diff --git a/src/rules/circuit_spinglass.rs b/src/rules/circuit_spinglass.rs
index 4fc837856..b31f73583 100644
--- a/src/rules/circuit_spinglass.rs
+++ b/src/rules/circuit_spinglass.rs
@@ -13,7 +13,7 @@ use crate::reduction;
 use crate::rules::registry::ReductionOverhead;
 use crate::rules::traits::{ReduceTo, ReductionResult};
 use crate::topology::SimpleGraph;
-use num_traits::{Bounded, Num, Zero};
+use num_traits::Zero;
 use std::collections::HashMap;
 use std::ops::AddAssign;
 
@@ -179,21 +179,18 @@ where
 
 /// Result of reducing CircuitSAT to SpinGlass.
 #[derive(Debug, Clone)]
-pub struct ReductionCircuitToSG<W> {
+pub struct ReductionCircuitToSG {
     /// The target SpinGlass problem.
-    target: SpinGlass<SimpleGraph, W>,
+    target: SpinGlass<SimpleGraph, i32>,
     /// Mapping from source variable names to spin indices.
     variable_map: HashMap<String, usize>,
     /// Source variable names in order.
     source_variables: Vec<String>,
 }
 
-impl<W> ReductionResult for ReductionCircuitToSG<W>
-where
-    W: Clone + Default + PartialOrd + Num + Zero + Bounded + AddAssign + From<i32> + 'static,
-{
-    type Source = CircuitSAT<W>;
-    type Target = SpinGlass<SimpleGraph, W>;
+impl ReductionResult for ReductionCircuitToSG {
+    type Source = CircuitSAT;
+    type Target = SpinGlass<SimpleGraph, i32>;
 
     fn target_problem(&self) -> &Self::Target {
         &self.target
@@ -422,14 +419,11 @@ where
         ])
     }
 )]
-impl<W> ReduceTo<SpinGlass<SimpleGraph, W>> for CircuitSAT<W>
-where
-    W: Clone + Default + PartialOrd + Num + Zero + Bounded + AddAssign + From<i32> + 'static,
-{
-    type Result = ReductionCircuitToSG<W>;
+impl ReduceTo<SpinGlass<SimpleGraph, i32>> for CircuitSAT {
+    type Result = ReductionCircuitToSG;
 
     fn reduce_to(&self) -> Self::Result {
-        let mut builder = SpinGlassBuilder::new();
+        let mut builder: SpinGlassBuilder<i32> = SpinGlassBuilder::new();
 
         // Process each assignment in the circuit
         for assignment in &self.circuit().assignments {
diff --git a/src/rules/coloring_ilp.rs b/src/rules/coloring_ilp.rs
index 7d36d4dad..dcb07bcf6 100644
--- a/src/rules/coloring_ilp.rs
+++ b/src/rules/coloring_ilp.rs
@@ -14,13 +14,15 @@ use crate::rules::registry::{ReductionEntry, ReductionOverhead};
 use crate::rules::traits::{ReduceTo, ReductionResult};
 use crate::topology::{Graph, SimpleGraph};
 
-// Register reduction in the inventory for automatic discovery
+// Register reduction in the inventory for automatic discovery.
+// Uses usize::MAX sentinel for K (maps to "N" via const_usize_str).
+// G is generic → defaults to SimpleGraph.
 inventory::submit! {
     ReductionEntry {
         source_name: "KColoring",
         target_name: "ILP",
-        source_variant: &[("k", "N"), ("graph", "SimpleGraph"), ("weight", "i32")],
-        target_variant: &[("graph", ""), ("weight", "Unweighted")],
+        source_variant_fn: || <KColoring<{usize::MAX}, SimpleGraph> as crate::traits::Problem>::variant(),
+        target_variant_fn: || <ILP as crate::traits::Problem>::variant(),
         overhead_fn: || ReductionOverhead::new(vec![
             ("num_vars", poly!(num_vertices * num_colors)),
             ("num_constraints", poly!(num_vertices) + poly!(num_edges * num_colors)),
@@ -36,32 +38,24 @@ inventory::submit! {
 /// - Constraints ensure each vertex has exactly one color
 /// - Constraints ensure adjacent vertices have different colors
 #[derive(Debug, Clone)]
-pub struct ReductionKColoringToILP<const K: usize, G, W> {
+pub struct ReductionKColoringToILP<const K: usize, G> {
     target: ILP,
     num_vertices: usize,
-    _phantom: std::marker::PhantomData<(G, W)>,
+    _phantom: std::marker::PhantomData<G>,
 }
 
-impl<const K: usize, G, W> ReductionKColoringToILP<K, G, W> {
+impl<const K: usize, G> ReductionKColoringToILP<K, G> {
     /// Get the variable index for vertex v with color c.
     fn var_index(&self, vertex: usize, color: usize) -> usize {
         vertex * K + color
     }
 }
 
-impl<const K: usize, G, W> ReductionResult for ReductionKColoringToILP<K, G, W>
+impl<const K: usize, G> ReductionResult for ReductionKColoringToILP<K, G>
 where
     G: Graph,
-    W: Clone
-        + Default
-        + PartialOrd
-        + num_traits::Num
-        + num_traits::Zero
-        + num_traits::Bounded
-        + std::ops::AddAssign
-        + 'static,
 {
-    type Source = KColoring<K, G, W>;
+    type Source = KColoring<K, G>;
     type Target = ILP;
 
     fn target_problem(&self) -> &ILP {
@@ -86,19 +80,11 @@ where
     }
 }
 
-impl<const K: usize, G, W> ReduceTo<ILP> for KColoring<K, G, W>
+impl<const K: usize, G> ReduceTo<ILP> for KColoring<K, G>
 where
     G: Graph,
-    W: Clone
-        + Default
-        + PartialOrd
-        + num_traits::Num
-        + num_traits::Zero
-        + num_traits::Bounded
-        + std::ops::AddAssign
-        + 'static,
 {
-    type Result = ReductionKColoringToILP<K, G, W>;
+    type Result = ReductionKColoringToILP<K, G>;
 
     fn reduce_to(&self) -> Self::Result {
         let num_vertices = self.num_vertices();
@@ -151,7 +137,7 @@ where
 }
 
 // Keep the old type alias for backwards compatibility
-pub type ReductionColoringToILP = ReductionKColoringToILP<3, SimpleGraph, i32>;
+pub type ReductionColoringToILP = ReductionKColoringToILP<3, SimpleGraph>;
 
 #[cfg(test)]
 #[path = "../unit_tests/rules/coloring_ilp.rs"]
diff --git a/src/rules/coloring_qubo.rs b/src/rules/coloring_qubo.rs
index ebd59a9f0..6e0649f4a 100644
--- a/src/rules/coloring_qubo.rs
+++ b/src/rules/coloring_qubo.rs
@@ -23,7 +23,7 @@ pub struct ReductionKColoringToQUBO<const K: usize> {
 }
 
 impl<const K: usize> ReductionResult for ReductionKColoringToQUBO<K> {
-    type Source = KColoring<K, SimpleGraph, i32>;
+    type Source = KColoring<K, SimpleGraph>;
     type Target = QUBO<f64>;
 
     fn target_problem(&self) -> &Self::Target {
@@ -45,7 +45,7 @@ impl<const K: usize> ReductionResult for ReductionKColoringToQUBO<K> {
 #[reduction(
     overhead = { ReductionOverhead::new(vec![("num_vars", poly!(num_vertices * num_colors))]) }
 )]
-impl<const K: usize> ReduceTo<QUBO<f64>> for KColoring<K, SimpleGraph, i32> {
+impl<const K: usize> ReduceTo<QUBO<f64>> for KColoring<K, SimpleGraph> {
     type Result = ReductionKColoringToQUBO<K>;
 
     fn reduce_to(&self) -> Self::Result {
diff --git a/src/rules/factoring_circuit.rs b/src/rules/factoring_circuit.rs
index 9fa50b5ec..607dedc15 100644
--- a/src/rules/factoring_circuit.rs
+++ b/src/rules/factoring_circuit.rs
@@ -22,7 +22,7 @@ use crate::rules::traits::{ReduceTo, ReductionResult};
 #[derive(Debug, Clone)]
 pub struct ReductionFactoringToCircuit {
     /// The target CircuitSAT problem.
-    target: CircuitSAT<i32>,
+    target: CircuitSAT,
     /// Variable names for the first factor p (bit positions).
     p_vars: Vec<String>,
     /// Variable names for the second factor q (bit positions).
@@ -33,7 +33,7 @@ pub struct ReductionFactoringToCircuit {
 
 impl ReductionResult for ReductionFactoringToCircuit {
     type Source = Factoring;
-    type Target = CircuitSAT<i32>;
+    type Target = CircuitSAT;
 
     fn target_problem(&self) -> &Self::Target {
         &self.target
@@ -181,7 +181,7 @@ fn build_multiplier_cell(
         ("num_gates", poly!(num_bits_first * num_bits_second)),
     ])
 })]
-impl ReduceTo<CircuitSAT<i32>> for Factoring {
+impl ReduceTo<CircuitSAT> for Factoring {
     type Result = ReductionFactoringToCircuit;
 
     fn reduce_to(&self) -> Self::Result {
diff --git a/src/rules/factoring_ilp.rs b/src/rules/factoring_ilp.rs
index e2dea4e01..eaf0cfc71 100644
--- a/src/rules/factoring_ilp.rs
+++ b/src/rules/factoring_ilp.rs
@@ -29,8 +29,8 @@ inventory::submit! {
     ReductionEntry {
         source_name: "Factoring",
         target_name: "ILP",
-        source_variant: &[("graph", ""), ("weight", "Unweighted")],
-        target_variant: &[("graph", ""), ("weight", "Unweighted")],
+        source_variant_fn: || <Factoring as crate::traits::Problem>::variant(),
+        target_variant_fn: || <ILP as crate::traits::Problem>::variant(),
         overhead_fn: || ReductionOverhead::new(vec![
             // num_vars = m + n + m*n + num_carries where num_carries = max(m+n, target_bits)
             // For feasible instances, target_bits <= m+n, so this is 2(m+n) + m*n
@@ -255,11 +255,7 @@ impl ReduceTo<ILP> for Factoring {
             ObjectiveSense::Minimize,
         );
 
-        ReductionFactoringToILP {
-            target: ilp,
-            m,
-            n,
-        }
+        ReductionFactoringToILP { target: ilp, m, n }
     }
 }
 
diff --git a/src/rules/graph.rs b/src/rules/graph.rs
index e4f3da639..a9e98c1fb 100644
--- a/src/rules/graph.rs
+++ b/src/rules/graph.rs
@@ -23,10 +23,10 @@ use std::collections::{BinaryHeap, HashMap, HashSet};
 
 // Register concrete variants for problems that support non-SimpleGraph graph types.
 // These generate additional nodes in the JSON export.
-inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant: &[("graph", "GridGraph"), ("weight", "Unweighted")] } }
-inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant: &[("graph", "UnitDiskGraph"), ("weight", "Unweighted")] } }
-inventory::submit! { ConcreteVariantEntry { name: "MaxCut", variant: &[("graph", "GridGraph"), ("weight", "Unweighted")] } }
-inventory::submit! { ConcreteVariantEntry { name: "SpinGlass", variant: &[("graph", "GridGraph"), ("weight", "f64")] } }
+inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant_fn: || vec![("graph", "GridGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "MaximumIndependentSet", variant_fn: || vec![("graph", "UnitDiskGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "MaxCut", variant_fn: || vec![("graph", "GridGraph"), ("weight", "Unweighted")] } }
+inventory::submit! { ConcreteVariantEntry { name: "SpinGlass", variant_fn: || vec![("graph", "GridGraph"), ("weight", "f64")] } }
 
 /// JSON-serializable representation of the reduction graph.
 #[derive(Debug, Clone, Serialize)]
@@ -118,9 +118,9 @@ impl ReductionPath {
 #[derive(Clone, Debug)]
 pub struct ReductionEdge {
     /// Source variant attributes as key-value pairs.
-    pub source_variant: &'static [(&'static str, &'static str)],
+    pub source_variant: Vec<(&'static str, &'static str)>,
     /// Target variant attributes as key-value pairs.
-    pub target_variant: &'static [(&'static str, &'static str)],
+    pub target_variant: Vec<(&'static str, &'static str)>,
     /// Overhead information for cost calculations.
     pub overhead: ReductionOverhead,
 }
@@ -128,7 +128,7 @@ pub struct ReductionEdge {
 impl ReductionEdge {
     /// Get the graph type from the source variant, or "SimpleGraph" as default.
     /// Empty strings are treated as missing and default to "SimpleGraph".
-    pub fn source_graph(&self) -> &'static str {
+    pub fn source_graph(&self) -> &str {
         self.source_variant
             .iter()
             .find(|(k, _)| *k == "graph")
@@ -139,7 +139,7 @@ impl ReductionEdge {
 
     /// Get the graph type from the target variant, or "SimpleGraph" as default.
     /// Empty strings are treated as missing and default to "SimpleGraph".
-    pub fn target_graph(&self) -> &'static str {
+    pub fn target_graph(&self) -> &str {
         self.target_variant
             .iter()
             .find(|(k, _)| *k == "graph")
@@ -212,8 +212,8 @@ impl ReductionGraph {
                     src,
                     dst,
                     ReductionEdge {
-                        source_variant: entry.source_variant,
-                        target_variant: entry.target_variant,
+                        source_variant: entry.source_variant(),
+                        target_variant: entry.target_variant(),
                         overhead: entry.overhead(),
                     },
                 );
@@ -355,7 +355,7 @@ impl ReductionGraph {
             MaxCut<SimpleGraph, f64> => "MaxCut",
             MaximumMatching<SimpleGraph, i32> => "MaximumMatching",
             MinimumDominatingSet<SimpleGraph, i32> => "MinimumDominatingSet",
-            KColoring<3, SimpleGraph, i32> => "KColoring",
+            KColoring<3, SimpleGraph> => "KColoring",
             // Set problems
             MaximumSetPacking<i32> => "MaximumSetPacking",
             MinimumSetCovering<i32> => "MinimumSetCovering",
@@ -367,7 +367,7 @@ impl ReductionGraph {
             // Satisfiability problems
             Satisfiability => "Satisfiability",
             KSatisfiability<3> => "KSatisfiability",
-            CircuitSAT<i32> => "CircuitSAT",
+            CircuitSAT => "CircuitSAT",
             // Specialized
             Factoring => "Factoring",
         }
@@ -626,6 +626,12 @@ impl Default for ReductionGraph {
     }
 }
 
+/// Check if const value `a` is a subtype of `b`.
+/// A specific value (e.g., "3") is a subtype of "N" (generic/any).
+fn is_const_subtype(a: &str, b: &str) -> bool {
+    a != b && b == "N" && a != "N"
+}
+
 impl ReductionGraph {
     /// Check if variant A is strictly more restrictive than variant B (same problem name).
     /// Returns true if every field of A is a subtype of (or equal to) the corresponding field in B,
@@ -656,6 +662,7 @@ impl ReductionGraph {
             let is_sub = match key.as_str() {
                 "graph" => self.is_graph_subtype(a_val, b_val),
                 "weight" => self.is_weight_subtype(a_val, b_val),
+                "k" => is_const_subtype(a_val, b_val),
                 _ => false, // Unknown fields must be equal
             };
 
@@ -671,9 +678,7 @@ impl ReductionGraph {
 
     /// Helper to convert a variant slice to a BTreeMap.
     /// Normalizes empty "graph" values to "SimpleGraph" for consistency.
-    fn variant_to_map(
-        variant: &[(&'static str, &'static str)],
-    ) -> std::collections::BTreeMap<String, String> {
+    fn variant_to_map(variant: &[(&str, &str)]) -> std::collections::BTreeMap<String, String> {
         variant
             .iter()
             .map(|(k, v)| {
@@ -688,7 +693,7 @@ impl ReductionGraph {
     }
 
     /// Helper to create a VariantRef from name and variant slice.
-    fn make_variant_ref(name: &str, variant: &[(&'static str, &'static str)]) -> VariantRef {
+    fn make_variant_ref(name: &str, variant: &[(&str, &str)]) -> VariantRef {
         VariantRef {
             name: name.to_string(),
             variant: Self::variant_to_map(variant),
@@ -712,19 +717,22 @@ impl ReductionGraph {
 
         // Then, collect variants from reduction entries
         for entry in inventory::iter::<ReductionEntry> {
+            let source_variant = entry.source_variant();
+            let target_variant = entry.target_variant();
             node_set.insert((
                 entry.source_name.to_string(),
-                Self::variant_to_map(entry.source_variant),
+                Self::variant_to_map(&source_variant),
             ));
             node_set.insert((
                 entry.target_name.to_string(),
-                Self::variant_to_map(entry.target_variant),
+                Self::variant_to_map(&target_variant),
             ));
         }
 
         // Also collect nodes from ConcreteVariantEntry registrations
         for entry in inventory::iter::<ConcreteVariantEntry> {
-            node_set.insert((entry.name.to_string(), Self::variant_to_map(entry.variant)));
+            let variant = (entry.variant_fn)();
+            node_set.insert((entry.name.to_string(), Self::variant_to_map(&variant)));
         }
 
         // Build nodes with categories and doc paths
@@ -748,8 +756,10 @@ impl ReductionGraph {
         let mut edge_data: Vec<(VariantRef, VariantRef, ReductionOverhead, String)> = Vec::new();
 
         for entry in inventory::iter::<ReductionEntry> {
-            let src_ref = Self::make_variant_ref(entry.source_name, entry.source_variant);
-            let dst_ref = Self::make_variant_ref(entry.target_name, entry.target_variant);
+            let source_variant = entry.source_variant();
+            let target_variant = entry.target_variant();
+            let src_ref = Self::make_variant_ref(entry.source_name, &source_variant);
+            let dst_ref = Self::make_variant_ref(entry.target_name, &target_variant);
             let key = (src_ref.clone(), dst_ref.clone());
             if edge_set.insert(key) {
                 let overhead = entry.overhead();
diff --git a/src/rules/maximumsetpacking_qubo.rs b/src/rules/maximumsetpacking_qubo.rs
index d39fc2971..3496eafbb 100644
--- a/src/rules/maximumsetpacking_qubo.rs
+++ b/src/rules/maximumsetpacking_qubo.rs
@@ -40,7 +40,9 @@ impl<W: NumericWeight + num_traits::Bounded + Into<f64>> ReductionResult for Red
     source_weighted = true,
     overhead = { ReductionOverhead::new(vec![("num_vars", poly!(num_sets))]) }
 )]
-impl<W: NumericWeight + num_traits::Bounded + Into<f64>> ReduceTo<QUBO<f64>> for MaximumSetPacking<W> {
+impl<W: NumericWeight + num_traits::Bounded + Into<f64>> ReduceTo<QUBO<f64>>
+    for MaximumSetPacking<W>
+{
     type Result = ReductionSPToQUBO<W>;
 
     fn reduce_to(&self) -> Self::Result {
diff --git a/src/rules/registry.rs b/src/rules/registry.rs
index df95f802d..476ee5026 100644
--- a/src/rules/registry.rs
+++ b/src/rules/registry.rs
@@ -33,17 +33,16 @@ impl ReductionOverhead {
 }
 
 /// A registered reduction entry for static inventory registration.
-/// Uses function pointer to lazily create the overhead (avoids static allocation issues).
+/// Uses function pointers to lazily derive variant fields from `Problem::variant()`.
 pub struct ReductionEntry {
     /// Base name of source problem (e.g., "MaximumIndependentSet").
     pub source_name: &'static str,
     /// Base name of target problem (e.g., "MinimumVertexCover").
     pub target_name: &'static str,
-    /// Variant attributes for source problem as key-value pairs.
-    /// Common keys: "graph" (graph type), "weight" (weight type).
-    pub source_variant: &'static [(&'static str, &'static str)],
-    /// Variant attributes for target problem as key-value pairs.
-    pub target_variant: &'static [(&'static str, &'static str)],
+    /// Function to derive source variant attributes from `Problem::variant()`.
+    pub source_variant_fn: fn() -> Vec<(&'static str, &'static str)>,
+    /// Function to derive target variant attributes from `Problem::variant()`.
+    pub target_variant_fn: fn() -> Vec<(&'static str, &'static str)>,
     /// Function to create overhead information (lazy evaluation for static context).
     pub overhead_fn: fn() -> ReductionOverhead,
     /// Module path where the reduction is defined (from `module_path!()`).
@@ -56,16 +55,26 @@ impl ReductionEntry {
         (self.overhead_fn)()
     }
 
+    /// Get the source variant by calling the function.
+    pub fn source_variant(&self) -> Vec<(&'static str, &'static str)> {
+        (self.source_variant_fn)()
+    }
+
+    /// Get the target variant by calling the function.
+    pub fn target_variant(&self) -> Vec<(&'static str, &'static str)> {
+        (self.target_variant_fn)()
+    }
+
     /// Check if this reduction involves only the base (unweighted) variants.
     pub fn is_base_reduction(&self) -> bool {
-        let source_unweighted = self
-            .source_variant
+        let source = self.source_variant();
+        let target = self.target_variant();
+        let source_unweighted = source
             .iter()
             .find(|(k, _)| *k == "weight")
             .map(|(_, v)| *v == "Unweighted")
             .unwrap_or(true);
-        let target_unweighted = self
-            .target_variant
+        let target_unweighted = target
             .iter()
             .find(|(k, _)| *k == "weight")
             .map(|(_, v)| *v == "Unweighted")
@@ -79,8 +88,8 @@ impl std::fmt::Debug for ReductionEntry {
         f.debug_struct("ReductionEntry")
             .field("source_name", &self.source_name)
             .field("target_name", &self.target_name)
-            .field("source_variant", &self.source_variant)
-            .field("target_variant", &self.target_variant)
+            .field("source_variant", &self.source_variant())
+            .field("target_variant", &self.target_variant())
             .field("overhead", &self.overhead())
             .field("module_path", &self.module_path)
             .finish()
@@ -93,7 +102,7 @@ inventory::collect!(ReductionEntry);
 /// Variants registered here appear as nodes even without explicit reduction rules.
 pub struct ConcreteVariantEntry {
     pub name: &'static str,
-    pub variant: &'static [(&'static str, &'static str)],
+    pub variant_fn: fn() -> Vec<(&'static str, &'static str)>,
 }
 
 inventory::collect!(ConcreteVariantEntry);
diff --git a/src/rules/sat_coloring.rs b/src/rules/sat_coloring.rs
index 6cf3e0583..e7fdfd789 100644
--- a/src/rules/sat_coloring.rs
+++ b/src/rules/sat_coloring.rs
@@ -199,8 +199,8 @@ impl SATColoringConstructor {
     }
 
     /// Build the final KColoring problem.
-    fn build_coloring(&self) -> KColoring<3, SimpleGraph, i32> {
-        KColoring::<3, SimpleGraph, i32>::new(self.num_vertices, self.edges.clone())
+    fn build_coloring(&self) -> KColoring<3, SimpleGraph> {
+        KColoring::<3, SimpleGraph>::new(self.num_vertices, self.edges.clone())
     }
 }
 
@@ -213,7 +213,7 @@ impl SATColoringConstructor {
 #[derive(Debug, Clone)]
 pub struct ReductionSATToColoring {
     /// The target KColoring problem.
-    target: KColoring<3, SimpleGraph, i32>,
+    target: KColoring<3, SimpleGraph>,
     /// Mapping from variable index (0-indexed) to positive literal vertex index.
     pos_vertices: Vec<usize>,
     /// Mapping from variable index (0-indexed) to negative literal vertex index.
@@ -226,7 +226,7 @@ pub struct ReductionSATToColoring {
 
 impl ReductionResult for ReductionSATToColoring {
     type Source = Satisfiability;
-    type Target = KColoring<3, SimpleGraph, i32>;
+    type Target = KColoring<3, SimpleGraph>;
 
     fn target_problem(&self) -> &Self::Target {
         &self.target
@@ -304,7 +304,7 @@ impl ReductionSATToColoring {
         ])
     }
 )]
-impl ReduceTo<KColoring<3, SimpleGraph, i32>> for Satisfiability {
+impl ReduceTo<KColoring<3, SimpleGraph>> for Satisfiability {
     type Result = ReductionSATToColoring;
 
     fn reduce_to(&self) -> Self::Result {
diff --git a/src/rules/sat_ksat.rs b/src/rules/sat_ksat.rs
index b38ba6b3c..f654b6e83 100644
--- a/src/rules/sat_ksat.rs
+++ b/src/rules/sat_ksat.rs
@@ -187,13 +187,14 @@ impl<const K: usize> ReduceTo<Satisfiability> for KSatisfiability<K> {
 #[path = "../unit_tests/rules/sat_ksat.rs"]
 mod tests;
 
-// Register SAT -> KSAT reduction manually (generated by macro, can't use #[reduction])
+// Register SAT -> KSAT reduction manually (generated by macro, can't use #[reduction]).
+// Uses usize::MAX sentinel for K (maps to "N" via const_usize_str).
 inventory::submit! {
     crate::rules::registry::ReductionEntry {
         source_name: "Satisfiability",
         target_name: "KSatisfiability",
-        source_variant: &[("graph", ""), ("weight", "Unweighted")],
-        target_variant: &[("graph", ""), ("weight", "Unweighted")],
+        source_variant_fn: || <Satisfiability as crate::traits::Problem>::variant(),
+        target_variant_fn: || <KSatisfiability<{usize::MAX}> as crate::traits::Problem>::variant(),
         overhead_fn: || ReductionOverhead::new(vec![
             ("num_clauses", poly!(num_clauses) + poly!(num_literals)),
             ("num_vars", poly!(num_vars) + poly!(num_literals)),
diff --git a/src/rules/unitdiskmapping/pathdecomposition.rs b/src/rules/unitdiskmapping/pathdecomposition.rs
index 0aad1605a..ec0ef889a 100644
--- a/src/rules/unitdiskmapping/pathdecomposition.rs
+++ b/src/rules/unitdiskmapping/pathdecomposition.rs
@@ -260,10 +260,7 @@ fn greedy_exact(adj: &AdjList, mut layout: Layout) -> Layout {
 ///
 /// Selects randomly among vertices that minimize the new vsep.
 fn greedy_step(adj: &AdjList, layout: &Layout, list: &[usize]) -> Layout {
-    let layouts: Vec<Layout> = list
-        .iter()
-        .map(|&v| extend(adj, layout, v))
-        .collect();
+    let layouts: Vec<Layout> = list.iter().map(|&v| extend(adj, layout, v)).collect();
 
     let costs: Vec<usize> = layouts.iter().map(|l| l.vsep()).collect();
     let best_cost = *costs.iter().min().unwrap();
diff --git a/src/solvers/brute_force.rs b/src/solvers/brute_force.rs
index 05fcf192d..e84b7190e 100644
--- a/src/solvers/brute_force.rs
+++ b/src/solvers/brute_force.rs
@@ -65,10 +65,7 @@ impl BruteForce {
     /// Find all satisfying solutions for constraint satisfaction problems.
     ///
     /// Returns all configurations where `problem.evaluate(config)` returns `true`.
-    pub fn find_all_satisfying<P: Problem<Metric = bool>>(
-        &self,
-        problem: &P,
-    ) -> Vec<Vec<usize>> {
+    pub fn find_all_satisfying<P: Problem<Metric = bool>>(&self, problem: &P) -> Vec<Vec<usize>> {
         DimsIterator::new(problem.dims())
             .filter(|config| problem.evaluate(config))
             .collect()
diff --git a/src/unit_tests/graph_models.rs b/src/unit_tests/graph_models.rs
index e04ce888f..97d897e5d 100644
--- a/src/unit_tests/graph_models.rs
+++ b/src/unit_tests/graph_models.rs
@@ -384,7 +384,7 @@ mod kcoloring {
 
     #[test]
     fn test_creation() {
-        let problem = KColoring::<3, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+        let problem = KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
         assert_eq!(problem.num_vertices(), 4);
         assert_eq!(problem.num_edges(), 3);
         assert_eq!(problem.num_colors(), 3);
@@ -393,7 +393,7 @@ mod kcoloring {
 
     #[test]
     fn test_evaluate_valid() {
-        let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+        let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
 
         // Valid: different colors on adjacent vertices - returns true
         assert!(problem.evaluate(&[0, 1, 0]));
@@ -402,7 +402,7 @@ mod kcoloring {
 
     #[test]
     fn test_evaluate_invalid() {
-        let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+        let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
 
         // Invalid: adjacent vertices have same color
         assert!(!problem.evaluate(&[0, 0, 1])); // 0-1 conflict
@@ -412,7 +412,7 @@ mod kcoloring {
     #[test]
     fn test_brute_force_path() {
         // Path graph can be 2-colored
-        let problem = KColoring::<2, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+        let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
         let solver = BruteForce::new();
 
         let solutions = solver.find_all_satisfying(&problem);
@@ -425,7 +425,7 @@ mod kcoloring {
     #[test]
     fn test_brute_force_triangle() {
         // Triangle needs 3 colors
-        let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+        let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
         let solver = BruteForce::new();
 
         let solutions = solver.find_all_satisfying(&problem);
@@ -441,7 +441,7 @@ mod kcoloring {
     #[test]
     fn test_triangle_2_colors_unsat() {
         // Triangle cannot be 2-colored
-        let problem = KColoring::<2, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+        let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
         let solver = BruteForce::new();
 
         // No satisfying assignments
@@ -463,7 +463,7 @@ mod kcoloring {
 
     #[test]
     fn test_empty_graph() {
-        let problem = KColoring::<1, SimpleGraph, i32>::new(3, vec![]);
+        let problem = KColoring::<1, SimpleGraph>::new(3, vec![]);
         let solver = BruteForce::new();
 
         let solutions = solver.find_all_satisfying(&problem);
@@ -474,7 +474,7 @@ mod kcoloring {
     #[test]
     fn test_complete_graph_k4() {
         // K4 needs 4 colors
-        let problem = KColoring::<4, SimpleGraph, i32>::new(
+        let problem = KColoring::<4, SimpleGraph>::new(
             4,
             vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
         );
diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs
index a9049afad..feae1b2ba 100644
--- a/src/unit_tests/models/graph/kcoloring.rs
+++ b/src/unit_tests/models/graph/kcoloring.rs
@@ -5,7 +5,7 @@ use crate::solvers::BruteForce;
 fn test_kcoloring_creation() {
     use crate::traits::Problem;
 
-    let problem = KColoring::<3, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+    let problem = KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
     assert_eq!(problem.num_vertices(), 4);
     assert_eq!(problem.num_edges(), 3);
     assert_eq!(problem.num_colors(), 3);
@@ -16,7 +16,7 @@ fn test_kcoloring_creation() {
 fn test_evaluate_valid() {
     use crate::traits::Problem;
 
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
 
     // Valid: different colors on adjacent vertices
     assert!(problem.evaluate(&[0, 1, 0]));
@@ -27,7 +27,7 @@ fn test_evaluate_valid() {
 fn test_evaluate_invalid() {
     use crate::traits::Problem;
 
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
 
     // Invalid: adjacent vertices have same color
     assert!(!problem.evaluate(&[0, 0, 1]));
@@ -39,7 +39,7 @@ fn test_brute_force_path() {
     use crate::traits::Problem;
 
     // Path graph can be 2-colored
-    let problem = KColoring::<2, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+    let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -54,7 +54,7 @@ fn test_brute_force_triangle() {
     use crate::traits::Problem;
 
     // Triangle needs 3 colors
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -70,7 +70,7 @@ fn test_brute_force_triangle() {
 #[test]
 fn test_triangle_2_colors() {
     // Triangle cannot be 2-colored
-    let problem = KColoring::<2, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -94,7 +94,7 @@ fn test_is_valid_coloring_function() {
 fn test_empty_graph() {
     use crate::traits::Problem;
 
-    let problem = KColoring::<1, SimpleGraph, i32>::new(3, vec![]);
+    let problem = KColoring::<1, SimpleGraph>::new(3, vec![]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -110,10 +110,8 @@ fn test_complete_graph_k4() {
     use crate::traits::Problem;
 
     // K4 needs 4 colors
-    let problem = KColoring::<4, SimpleGraph, i32>::new(
-        4,
-        vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
-    );
+    let problem =
+        KColoring::<4, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -125,7 +123,7 @@ fn test_complete_graph_k4() {
 #[test]
 fn test_from_graph() {
     let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]);
-    let problem = KColoring::<3, SimpleGraph, i32>::from_graph(graph);
+    let problem = KColoring::<3, SimpleGraph>::from_graph(graph);
     assert_eq!(problem.num_vertices(), 3);
     assert_eq!(problem.num_edges(), 2);
 }
@@ -135,7 +133,7 @@ fn test_kcoloring_problem() {
     use crate::traits::Problem;
 
     // Triangle graph with 3 colors
-    let p = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let p = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     assert_eq!(p.dims(), vec![3, 3, 3]);
     // Valid: each vertex different color
     assert!(p.evaluate(&[0, 1, 2]));
diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs
index 309ef4dac..1b2ee56e9 100644
--- a/src/unit_tests/models/graph/maximum_matching.rs
+++ b/src/unit_tests/models/graph/maximum_matching.rs
@@ -47,10 +47,16 @@ fn test_evaluate() {
         MaximumMatching::<SimpleGraph, i32>::new(4, vec![(0, 1, 5), (1, 2, 10), (2, 3, 3)]);
 
     // Valid matching: edges 0 and 2 (disjoint)
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 1]), SolutionSize::Valid(8)); // 5 + 3
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 1]),
+        SolutionSize::Valid(8)
+    ); // 5 + 3
 
     // Valid matching: edge 1 only
-    assert_eq!(Problem::evaluate(&problem, &[0, 1, 0]), SolutionSize::Valid(10));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1, 0]),
+        SolutionSize::Valid(10)
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs
index 9863b8067..ad0a92b91 100644
--- a/src/unit_tests/models/graph/minimum_dominating_set.rs
+++ b/src/unit_tests/models/graph/minimum_dominating_set.rs
@@ -42,10 +42,16 @@ fn test_evaluate_valid() {
     let problem = MinimumDominatingSet::<SimpleGraph, i32>::new(4, vec![(0, 1), (0, 2), (0, 3)]);
 
     // Select center
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 0, 0]), SolutionSize::Valid(1));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 0, 0]),
+        SolutionSize::Valid(1)
+    );
 
     // Select all leaves
-    assert_eq!(Problem::evaluate(&problem, &[0, 1, 1, 1]), SolutionSize::Valid(3));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1, 1, 1]),
+        SolutionSize::Valid(3)
+    );
 }
 
 #[test]
@@ -53,10 +59,16 @@ fn test_evaluate_invalid() {
     let problem = MinimumDominatingSet::<SimpleGraph, i32>::new(4, vec![(0, 1), (2, 3)]);
 
     // Select none - returns Invalid for minimization
-    assert_eq!(Problem::evaluate(&problem, &[0, 0, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0, 0, 0]),
+        SolutionSize::Invalid
+    );
 
     // Select only vertex 0 (doesn't dominate 2, 3)
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 0, 0]),
+        SolutionSize::Invalid
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs
index a4662b86c..899bcca63 100644
--- a/src/unit_tests/models/graph/minimum_vertex_cover.rs
+++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs
@@ -23,10 +23,16 @@ fn test_evaluate_valid() {
     let problem = MinimumVertexCover::<SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
 
     // Valid: select vertex 1 (covers both edges)
-    assert_eq!(Problem::evaluate(&problem, &[0, 1, 0]), SolutionSize::Valid(1));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1, 0]),
+        SolutionSize::Valid(1)
+    );
 
     // Valid: select all vertices
-    assert_eq!(Problem::evaluate(&problem, &[1, 1, 1]), SolutionSize::Valid(3));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1, 1]),
+        SolutionSize::Valid(3)
+    );
 }
 
 #[test]
@@ -34,10 +40,16 @@ fn test_evaluate_invalid() {
     let problem = MinimumVertexCover::<SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
 
     // Invalid: no vertex selected - returns Invalid for minimization
-    assert_eq!(Problem::evaluate(&problem, &[0, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0, 0]),
+        SolutionSize::Invalid
+    );
 
     // Invalid: only vertex 0 selected (edge 1-2 not covered)
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 0]),
+        SolutionSize::Invalid
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/optimization/ilp.rs b/src/unit_tests/models/optimization/ilp.rs
index 7e7315703..8bd1968aa 100644
--- a/src/unit_tests/models/optimization/ilp.rs
+++ b/src/unit_tests/models/optimization/ilp.rs
@@ -411,7 +411,10 @@ fn test_ilp_brute_force_no_feasible() {
     let solutions = solver.find_all_best(&ilp);
 
     // All solutions are infeasible - BruteForce should return empty list
-    assert!(solutions.is_empty(), "Expected no solutions for infeasible ILP");
+    assert!(
+        solutions.is_empty(),
+        "Expected no solutions for infeasible ILP"
+    );
 
     // Verify all configs are indeed infeasible
     for config in &[[0], [1]] {
diff --git a/src/unit_tests/models/optimization/qubo.rs b/src/unit_tests/models/optimization/qubo.rs
index 9b136c765..a587614e3 100644
--- a/src/unit_tests/models/optimization/qubo.rs
+++ b/src/unit_tests/models/optimization/qubo.rs
@@ -26,10 +26,22 @@ fn test_evaluate() {
     // f(x) = x0 + 3*x1 + 2*x0*x1
     let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]);
 
-    assert_eq!(Problem::evaluate(&problem, &[0, 0]), SolutionSize::Valid(0.0));
-    assert_eq!(Problem::evaluate(&problem, &[1, 0]), SolutionSize::Valid(1.0));
-    assert_eq!(Problem::evaluate(&problem, &[0, 1]), SolutionSize::Valid(3.0));
-    assert_eq!(Problem::evaluate(&problem, &[1, 1]), SolutionSize::Valid(6.0)); // 1 + 3 + 2 = 6
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0]),
+        SolutionSize::Valid(0.0)
+    );
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0]),
+        SolutionSize::Valid(1.0)
+    );
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1]),
+        SolutionSize::Valid(3.0)
+    );
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1]),
+        SolutionSize::Valid(6.0)
+    ); // 1 + 3 + 2 = 6
 }
 
 #[test]
@@ -43,7 +55,10 @@ fn test_brute_force_minimize() {
     let solutions = solver.find_all_best(&problem);
     assert_eq!(solutions.len(), 1);
     assert_eq!(solutions[0], vec![0, 1]);
-    assert_eq!(Problem::evaluate(&problem, &solutions[0]), SolutionSize::Valid(-2.0));
+    assert_eq!(
+        Problem::evaluate(&problem, &solutions[0]),
+        SolutionSize::Valid(-2.0)
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/optimization/spin_glass.rs b/src/unit_tests/models/optimization/spin_glass.rs
index c075722ed..cec4fb56f 100644
--- a/src/unit_tests/models/optimization/spin_glass.rs
+++ b/src/unit_tests/models/optimization/spin_glass.rs
@@ -71,10 +71,16 @@ fn test_evaluate() {
     let problem = SpinGlass::<SimpleGraph, f64>::new(2, vec![((0, 1), 1.0)], vec![0.0, 0.0]);
 
     // config [0,0] -> spins [-1,-1] -> energy = 1
-    assert_eq!(Problem::evaluate(&problem, &[0, 0]), SolutionSize::Valid(1.0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0]),
+        SolutionSize::Valid(1.0)
+    );
 
     // config [0,1] -> spins [-1,1] -> energy = -1
-    assert_eq!(Problem::evaluate(&problem, &[0, 1]), SolutionSize::Valid(-1.0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1]),
+        SolutionSize::Valid(-1.0)
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/satisfiability/ksat.rs b/src/unit_tests/models/satisfiability/ksat.rs
index 7c5d28ce0..47a56c989 100644
--- a/src/unit_tests/models/satisfiability/ksat.rs
+++ b/src/unit_tests/models/satisfiability/ksat.rs
@@ -131,10 +131,7 @@ fn test_ksat_problem_v2() {
     assert!(!p.evaluate(&[1, 1, 1]));
     assert!(!p.evaluate(&[0, 0, 0]));
     assert!(p.evaluate(&[1, 0, 1]));
-    assert_eq!(
-        <KSatisfiability<3> as Problem>::NAME,
-        "KSatisfiability"
-    );
+    assert_eq!(<KSatisfiability<3> as Problem>::NAME, "KSatisfiability");
 }
 
 #[test]
diff --git a/src/unit_tests/models/satisfiability/sat.rs b/src/unit_tests/models/satisfiability/sat.rs
index bb3e77b5d..04e74e234 100644
--- a/src/unit_tests/models/satisfiability/sat.rs
+++ b/src/unit_tests/models/satisfiability/sat.rs
@@ -161,8 +161,7 @@ fn test_zero_vars_unsat_solver() {
 #[test]
 fn test_single_literal_clauses() {
     // Unit propagation scenario: x1 AND NOT x2
-    let problem =
-        Satisfiability::new(2, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-2])]);
+    let problem = Satisfiability::new(2, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-2])]);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs
index 2bbda678c..06ce8e7ab 100644
--- a/src/unit_tests/models/set/maximum_set_packing.rs
+++ b/src/unit_tests/models/set/maximum_set_packing.rs
@@ -40,10 +40,16 @@ fn test_evaluate_valid() {
     let problem = MaximumSetPacking::<i32>::new(vec![vec![0, 1], vec![2, 3], vec![4, 5]]);
 
     // All disjoint, can select all
-    assert_eq!(Problem::evaluate(&problem, &[1, 1, 1]), SolutionSize::Valid(3));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1, 1]),
+        SolutionSize::Valid(3)
+    );
 
     // Select none - valid with size 0
-    assert_eq!(Problem::evaluate(&problem, &[0, 0, 0]), SolutionSize::Valid(0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0, 0]),
+        SolutionSize::Valid(0)
+    );
 }
 
 #[test]
@@ -51,7 +57,10 @@ fn test_evaluate_invalid() {
     let problem = MaximumSetPacking::<i32>::new(vec![vec![0, 1], vec![1, 2], vec![3, 4]]);
 
     // Sets 0 and 1 overlap - returns Invalid
-    assert_eq!(Problem::evaluate(&problem, &[1, 1, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1, 0]),
+        SolutionSize::Invalid
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs
index bbcbc7010..3746d72ab 100644
--- a/src/unit_tests/models/set/minimum_set_covering.rs
+++ b/src/unit_tests/models/set/minimum_set_covering.rs
@@ -38,10 +38,16 @@ fn test_evaluate_valid() {
     let problem = MinimumSetCovering::<i32>::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]);
 
     // Select first and third sets: covers {0,1} + {2,3} = {0,1,2,3}
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 1]), SolutionSize::Valid(2));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 1]),
+        SolutionSize::Valid(2)
+    );
 
     // Select all sets
-    assert_eq!(Problem::evaluate(&problem, &[1, 1, 1]), SolutionSize::Valid(3));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1, 1]),
+        SolutionSize::Valid(3)
+    );
 }
 
 #[test]
@@ -49,10 +55,16 @@ fn test_evaluate_invalid() {
     let problem = MinimumSetCovering::<i32>::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]);
 
     // Select only first set: missing 2, 3 - returns Invalid
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 0]),
+        SolutionSize::Invalid
+    );
 
     // Select none
-    assert_eq!(Problem::evaluate(&problem, &[0, 0, 0]), SolutionSize::Invalid);
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0, 0]),
+        SolutionSize::Invalid
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/models/specialized/biclique_cover.rs b/src/unit_tests/models/specialized/biclique_cover.rs
index 5f58c15e9..034179bb0 100644
--- a/src/unit_tests/models/specialized/biclique_cover.rs
+++ b/src/unit_tests/models/specialized/biclique_cover.rs
@@ -166,5 +166,8 @@ fn test_biclique_problem() {
 
     // Test with no edges: any config is valid
     let empty_problem = BicliqueCover::new(2, 2, vec![], 1);
-    assert_eq!(empty_problem.evaluate(&[0, 0, 0, 0]), SolutionSize::Valid(0));
+    assert_eq!(
+        empty_problem.evaluate(&[0, 0, 0, 0]),
+        SolutionSize::Valid(0)
+    );
 }
diff --git a/src/unit_tests/models/specialized/bmf.rs b/src/unit_tests/models/specialized/bmf.rs
index d68b12edb..f147dc107 100644
--- a/src/unit_tests/models/specialized/bmf.rs
+++ b/src/unit_tests/models/specialized/bmf.rs
@@ -183,10 +183,16 @@ fn test_bmf_problem() {
 
     // Exact factorization: B = I, C = I
     // Config: [1,0,0,1, 1,0,0,1]
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 0, 1, 1, 0, 0, 1]), SolutionSize::Valid(0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 0, 1, 1, 0, 0, 1]),
+        SolutionSize::Valid(0)
+    );
 
     // All zeros -> product is all zeros, distance = 2
-    assert_eq!(Problem::evaluate(&problem, &[0, 0, 0, 0, 0, 0, 0, 0]), SolutionSize::Valid(2));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 0, 0, 0, 0, 0, 0, 0]),
+        SolutionSize::Valid(2)
+    );
 
     // Direction is minimize
     assert_eq!(problem.direction(), Direction::Minimize);
diff --git a/src/unit_tests/models/specialized/circuit.rs b/src/unit_tests/models/specialized/circuit.rs
index eb196ffa2..e786ef196 100644
--- a/src/unit_tests/models/specialized/circuit.rs
+++ b/src/unit_tests/models/specialized/circuit.rs
@@ -121,7 +121,7 @@ fn test_circuit_sat_creation() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     assert_eq!(problem.num_variables(), 3); // c, x, y
     assert_eq!(problem.dims(), vec![2, 2, 2]); // binary variables
 }
@@ -133,7 +133,7 @@ fn test_circuit_sat_evaluate() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
 
     // Variables sorted: c, x, y
     // c=1, x=1, y=1 -> c = 1 AND 1 = 1, valid
@@ -153,7 +153,7 @@ fn test_circuit_sat_brute_force() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -179,7 +179,7 @@ fn test_circuit_sat_complex() {
             BooleanExpr::or(vec![BooleanExpr::var("c"), BooleanExpr::var("z")]),
         ),
     ]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let solver = BruteForce::new();
 
     let solutions = solver.find_all_satisfying(&problem);
@@ -209,7 +209,7 @@ fn test_is_circuit_satisfying() {
 #[test]
 fn test_empty_circuit() {
     let circuit = Circuit::new(vec![]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     // Empty circuit is trivially satisfied
     assert!(problem.evaluate(&[]));
 }
@@ -223,7 +223,7 @@ fn test_circuit_sat_problem() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let p = CircuitSAT::<i32>::new(circuit);
+    let p = CircuitSAT::new(circuit);
 
     // Variables sorted: c, x, y
     assert_eq!(p.dims(), vec![2, 2, 2]);
diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs
index a328894e6..6ad18e873 100644
--- a/src/unit_tests/models/specialized/factoring.rs
+++ b/src/unit_tests/models/specialized/factoring.rs
@@ -45,20 +45,32 @@ fn test_read_factors() {
 fn test_evaluate_valid() {
     let problem = Factoring::new(2, 2, 6);
     // 2 * 3 = 6 -> distance 0
-    assert_eq!(Problem::evaluate(&problem, &[0, 1, 1, 1]), SolutionSize::Valid(0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1, 1, 1]),
+        SolutionSize::Valid(0)
+    );
 
     // 3 * 2 = 6 -> distance 0
-    assert_eq!(Problem::evaluate(&problem, &[1, 1, 0, 1]), SolutionSize::Valid(0));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 1, 0, 1]),
+        SolutionSize::Valid(0)
+    );
 }
 
 #[test]
 fn test_evaluate_invalid() {
     let problem = Factoring::new(2, 2, 6);
     // 2 * 2 = 4 != 6 -> distance 2
-    assert_eq!(Problem::evaluate(&problem, &[0, 1, 0, 1]), SolutionSize::Valid(2));
+    assert_eq!(
+        Problem::evaluate(&problem, &[0, 1, 0, 1]),
+        SolutionSize::Valid(2)
+    );
 
     // 1 * 1 = 1 != 6 -> distance 5
-    assert_eq!(Problem::evaluate(&problem, &[1, 0, 1, 0]), SolutionSize::Valid(5));
+    assert_eq!(
+        Problem::evaluate(&problem, &[1, 0, 1, 0]),
+        SolutionSize::Valid(5)
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/circuit_spinglass.rs b/src/unit_tests/rules/circuit_spinglass.rs
index da30a78ea..1ab289689 100644
--- a/src/unit_tests/rules/circuit_spinglass.rs
+++ b/src/unit_tests/rules/circuit_spinglass.rs
@@ -2,6 +2,7 @@ use super::*;
 use crate::models::specialized::Circuit;
 use crate::solvers::BruteForce;
 use crate::types::NumericSize;
+use num_traits::Num;
 
 /// Verify a gadget has the correct ground states.
 fn verify_gadget_truth_table<W>(gadget: &LogicGadget<W>, expected: &[(Vec<usize>, Vec<usize>)])
@@ -144,7 +145,7 @@ fn test_simple_and_circuit() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -183,7 +184,7 @@ fn test_simple_or_circuit() {
         vec!["c".to_string()],
         BooleanExpr::or(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -220,7 +221,7 @@ fn test_not_circuit() {
         vec!["c".to_string()],
         BooleanExpr::not(BooleanExpr::var("x")),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -255,7 +256,7 @@ fn test_xor_circuit() {
         vec!["c".to_string()],
         BooleanExpr::xor(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -292,7 +293,7 @@ fn test_constant_true() {
         vec!["c".to_string()],
         BooleanExpr::constant(true),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -319,7 +320,7 @@ fn test_constant_false() {
         vec!["c".to_string()],
         BooleanExpr::constant(false),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -350,7 +351,7 @@ fn test_multi_input_and() {
             BooleanExpr::var("z"),
         ]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -391,7 +392,7 @@ fn test_chained_circuit() {
             BooleanExpr::or(vec![BooleanExpr::var("c"), BooleanExpr::var("z")]),
         ),
     ]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -439,7 +440,7 @@ fn test_nested_expression() {
             BooleanExpr::var("z"),
         ]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -482,7 +483,7 @@ fn test_reduction_result_methods() {
         vec!["c".to_string()],
         BooleanExpr::var("x"),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
 
     // Test target_problem and extract_solution work
@@ -493,7 +494,7 @@ fn test_reduction_result_methods() {
 #[test]
 fn test_empty_circuit() {
     let circuit = Circuit::new(vec![]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
     let sg = reduction.target_problem();
 
@@ -507,7 +508,7 @@ fn test_solution_extraction() {
         vec!["c".to_string()],
         BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
     )]);
-    let problem = CircuitSAT::<i32>::new(circuit);
+    let problem = CircuitSAT::new(circuit);
     let reduction = problem.reduce_to();
 
     // The source variables are c, x, y (sorted)
diff --git a/src/unit_tests/rules/coloring_ilp.rs b/src/unit_tests/rules/coloring_ilp.rs
index e7cd3ad8d..ffdd6a9cd 100644
--- a/src/unit_tests/rules/coloring_ilp.rs
+++ b/src/unit_tests/rules/coloring_ilp.rs
@@ -5,7 +5,7 @@ use crate::traits::Problem;
 #[test]
 fn test_reduction_creates_valid_ilp() {
     // Triangle graph with 3 colors
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -36,7 +36,7 @@ fn test_reduction_creates_valid_ilp() {
 #[test]
 fn test_reduction_path_graph() {
     // Path graph 0-1-2 with 2 colors (2-colorable)
-    let problem = KColoring::<2, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+    let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -50,7 +50,7 @@ fn test_reduction_path_graph() {
 #[test]
 fn test_ilp_solution_equals_brute_force_triangle() {
     // Triangle needs 3 colors
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -69,7 +69,10 @@ fn test_ilp_solution_equals_brute_force_triangle() {
     let extracted = reduction.extract_solution(&ilp_solution);
 
     // Verify the extracted solution is valid for the original problem
-    assert!(problem.evaluate(&extracted), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted),
+        "Extracted solution should be valid"
+    );
 
     // All three vertices should have different colors
     assert_ne!(extracted[0], extracted[1]);
@@ -80,7 +83,7 @@ fn test_ilp_solution_equals_brute_force_triangle() {
 #[test]
 fn test_ilp_solution_equals_brute_force_path() {
     // Path graph 0-1-2-3 with 2 colors
-    let problem = KColoring::<2, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+    let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -91,7 +94,10 @@ fn test_ilp_solution_equals_brute_force_path() {
     let extracted = reduction.extract_solution(&ilp_solution);
 
     // Verify validity
-    assert!(problem.evaluate(&extracted), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted),
+        "Extracted solution should be valid"
+    );
 
     // Check adjacent vertices have different colors
     assert_ne!(extracted[0], extracted[1]);
@@ -102,7 +108,7 @@ fn test_ilp_solution_equals_brute_force_path() {
 #[test]
 fn test_ilp_infeasible_triangle_2_colors() {
     // Triangle cannot be 2-colored
-    let problem = KColoring::<2, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -118,7 +124,7 @@ fn test_ilp_infeasible_triangle_2_colors() {
 
 #[test]
 fn test_solution_extraction() {
-    let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1)]);
+    let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
 
     // ILP solution where:
@@ -137,7 +143,7 @@ fn test_solution_extraction() {
 
 #[test]
 fn test_ilp_structure() {
-    let problem = KColoring::<3, SimpleGraph, i32>::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
+    let problem = KColoring::<3, SimpleGraph>::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -150,7 +156,7 @@ fn test_ilp_structure() {
 #[test]
 fn test_empty_graph() {
     // Graph with no edges: any coloring is valid
-    let problem = KColoring::<1, SimpleGraph, i32>::new(3, vec![]);
+    let problem = KColoring::<1, SimpleGraph>::new(3, vec![]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -167,10 +173,8 @@ fn test_empty_graph() {
 #[test]
 fn test_complete_graph_k4() {
     // K4 needs 4 colors
-    let problem = KColoring::<4, SimpleGraph, i32>::new(
-        4,
-        vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
-    );
+    let problem =
+        KColoring::<4, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -190,10 +194,8 @@ fn test_complete_graph_k4() {
 #[test]
 fn test_complete_graph_k4_with_3_colors_infeasible() {
     // K4 cannot be 3-colored
-    let problem = KColoring::<3, SimpleGraph, i32>::new(
-        4,
-        vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
-    );
+    let problem =
+        KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -206,7 +208,7 @@ fn test_complete_graph_k4_with_3_colors_infeasible() {
 fn test_bipartite_graph() {
     // Complete bipartite K_{2,2}: 0-2, 0-3, 1-2, 1-3
     // This is 2-colorable
-    let problem = KColoring::<2, SimpleGraph, i32>::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]);
+    let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -226,7 +228,7 @@ fn test_bipartite_graph() {
 #[test]
 fn test_solve_reduced() {
     // Test the ILPSolver::solve_reduced method
-    let problem = KColoring::<2, SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
+    let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
 
     let ilp_solver = ILPSolver::new();
     let solution = ilp_solver
@@ -239,7 +241,7 @@ fn test_solve_reduced() {
 #[test]
 fn test_single_vertex() {
     // Single vertex graph: always 1-colorable
-    let problem = KColoring::<1, SimpleGraph, i32>::new(1, vec![]);
+    let problem = KColoring::<1, SimpleGraph>::new(1, vec![]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
@@ -256,7 +258,7 @@ fn test_single_vertex() {
 #[test]
 fn test_single_edge() {
     // Single edge: needs 2 colors
-    let problem = KColoring::<2, SimpleGraph, i32>::new(2, vec![(0, 1)]);
+    let problem = KColoring::<2, SimpleGraph>::new(2, vec![(0, 1)]);
     let reduction = ReduceTo::<ILP>::reduce_to(&problem);
     let ilp = reduction.target_problem();
 
diff --git a/src/unit_tests/rules/coloring_qubo.rs b/src/unit_tests/rules/coloring_qubo.rs
index e353e398e..5d1b80c40 100644
--- a/src/unit_tests/rules/coloring_qubo.rs
+++ b/src/unit_tests/rules/coloring_qubo.rs
@@ -5,7 +5,7 @@ use crate::traits::Problem;
 #[test]
 fn test_kcoloring_to_qubo_closed_loop() {
     // Triangle K3, 3 colors → exactly 6 valid colorings (3! permutations)
-    let kc = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let kc = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let reduction = ReduceTo::<QUBO<f64>>::reduce_to(&kc);
     let qubo = reduction.target_problem();
 
@@ -25,7 +25,7 @@ fn test_kcoloring_to_qubo_closed_loop() {
 #[test]
 fn test_kcoloring_to_qubo_path() {
     // Path graph: 0-1-2, 2 colors
-    let kc = KColoring::<2, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+    let kc = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
     let reduction = ReduceTo::<QUBO<f64>>::reduce_to(&kc);
     let qubo = reduction.target_problem();
 
@@ -45,7 +45,7 @@ fn test_kcoloring_to_qubo_path() {
 fn test_kcoloring_to_qubo_reversed_edges() {
     // Edge (2, 0) triggers the idx_v < idx_u swap branch (line 104).
     // Path: 2-0-1 with reversed edge ordering
-    let kc = KColoring::<2, SimpleGraph, i32>::new(3, vec![(2, 0), (0, 1)]);
+    let kc = KColoring::<2, SimpleGraph>::new(3, vec![(2, 0), (0, 1)]);
     let reduction = ReduceTo::<QUBO<f64>>::reduce_to(&kc);
     let qubo = reduction.target_problem();
 
@@ -63,7 +63,7 @@ fn test_kcoloring_to_qubo_reversed_edges() {
 
 #[test]
 fn test_kcoloring_to_qubo_sizes() {
-    let kc = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
+    let kc = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
     let reduction = ReduceTo::<QUBO<f64>>::reduce_to(&kc);
 
     // QUBO should have n*K = 3*3 = 9 variables
diff --git a/src/unit_tests/rules/factoring_circuit.rs b/src/unit_tests/rules/factoring_circuit.rs
index 13f9a7bd2..39abea60f 100644
--- a/src/unit_tests/rules/factoring_circuit.rs
+++ b/src/unit_tests/rules/factoring_circuit.rs
@@ -22,7 +22,7 @@ fn test_read_bit() {
 fn test_reduction_structure() {
     // Factor 6 = 2 * 3 with 2-bit factors
     let factoring = Factoring::new(2, 2, 6);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     assert_eq!(reduction.p_vars().len(), 2);
     assert_eq!(reduction.q_vars().len(), 2);
@@ -33,7 +33,7 @@ fn test_reduction_structure() {
 fn test_reduction_structure_3x3() {
     // Factor 15 = 3 * 5 with 3-bit factors
     let factoring = Factoring::new(3, 3, 15);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     assert_eq!(reduction.p_vars().len(), 3);
     assert_eq!(reduction.q_vars().len(), 3);
@@ -99,7 +99,7 @@ fn check_factorization_satisfies(
 #[test]
 fn test_factorization_6_satisfies_circuit() {
     let factoring = Factoring::new(2, 2, 6);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     // 2 * 3 = 6 should satisfy the circuit
     assert!(
@@ -129,7 +129,7 @@ fn test_factorization_6_satisfies_circuit() {
 #[test]
 fn test_factorization_15_satisfies_circuit() {
     let factoring = Factoring::new(4, 4, 15);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     // Valid factorizations of 15
     assert!(
@@ -159,7 +159,7 @@ fn test_factorization_15_satisfies_circuit() {
 #[test]
 fn test_factorization_21_satisfies_circuit() {
     let factoring = Factoring::new(3, 3, 21);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     // 3 * 7 = 21
     assert!(
@@ -181,7 +181,7 @@ fn test_factorization_21_satisfies_circuit() {
 #[test]
 fn test_target_problem_structure() {
     let factoring = Factoring::new(3, 4, 15);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
     let circuit = reduction.target_problem();
 
     // Verify the circuit has variables and assignments
@@ -192,7 +192,7 @@ fn test_target_problem_structure() {
 #[test]
 fn test_extract_solution() {
     let factoring = Factoring::new(2, 2, 6);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
     let circuit_sat = reduction.target_problem();
 
     // Create a solution where p=2 (binary: 01) and q=3 (binary: 11)
@@ -224,7 +224,7 @@ fn test_extract_solution() {
 #[test]
 fn test_prime_7_only_trivial_factorizations() {
     let factoring = Factoring::new(3, 3, 7);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     // Check that only trivial factorizations satisfy
     for p in 0..8u64 {
@@ -257,7 +257,7 @@ fn test_prime_7_only_trivial_factorizations() {
 fn test_all_2bit_factorizations() {
     // Test all possible 2-bit * 2-bit multiplications for target 6
     let factoring = Factoring::new(2, 2, 6);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     let mut valid_factorizations = Vec::new();
     for p in 0..4u64 {
@@ -282,7 +282,7 @@ fn test_all_2bit_factorizations() {
 fn test_factorization_1_trivial() {
     // Factor 1 = 1 * 1
     let factoring = Factoring::new(2, 2, 1);
-    let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
+    let reduction = ReduceTo::<CircuitSAT>::reduce_to(&factoring);
 
     assert!(
         check_factorization_satisfies(&factoring, &reduction, 1, 1),
diff --git a/src/unit_tests/rules/graph.rs b/src/unit_tests/rules/graph.rs
index 861000236..f2702e06d 100644
--- a/src/unit_tests/rules/graph.rs
+++ b/src/unit_tests/rules/graph.rs
@@ -221,7 +221,7 @@ fn test_categorize_type() {
         "satisfiability"
     );
     assert_eq!(
-        ReductionGraph::categorize_type("CircuitSAT<i32>"),
+        ReductionGraph::categorize_type("CircuitSAT"),
         "satisfiability"
     );
 
@@ -244,7 +244,7 @@ fn test_sat_based_reductions() {
     assert!(graph.has_direct_reduction::<Satisfiability, MaximumIndependentSet<SimpleGraph, i32>>());
 
     // SAT -> KColoring
-    assert!(graph.has_direct_reduction::<Satisfiability, KColoring<3, SimpleGraph, i32>>());
+    assert!(graph.has_direct_reduction::<Satisfiability, KColoring<3, SimpleGraph>>());
 
     // SAT -> MinimumDominatingSet
     assert!(graph.has_direct_reduction::<Satisfiability, MinimumDominatingSet<SimpleGraph, i32>>());
@@ -258,10 +258,10 @@ fn test_circuit_reductions() {
     let graph = ReductionGraph::new();
 
     // Factoring -> CircuitSAT
-    assert!(graph.has_direct_reduction::<Factoring, CircuitSAT<i32>>());
+    assert!(graph.has_direct_reduction::<Factoring, CircuitSAT>());
 
     // CircuitSAT -> SpinGlass
-    assert!(graph.has_direct_reduction::<CircuitSAT<i32>, SpinGlass<SimpleGraph, f64>>());
+    assert!(graph.has_direct_reduction::<CircuitSAT, SpinGlass<SimpleGraph, i32>>());
 
     // Find path from Factoring to SpinGlass
     let paths = graph.find_paths::<Factoring, SpinGlass<SimpleGraph, f64>>();
@@ -422,11 +422,10 @@ fn test_find_shortest_path_no_path() {
 fn test_categorize_circuit_as_specialized() {
     // CircuitSAT should be categorized as specialized (contains "Circuit")
     assert_eq!(
-        ReductionGraph::categorize_type("CircuitSAT<i32>"),
+        ReductionGraph::categorize_type("CircuitSAT"),
         "satisfiability"
     );
-    // But it contains "SAT" so it goes to satisfiability first
-    // Let's verify the actual behavior matches what the code does
+    // It contains "SAT" so it goes to satisfiability
 }
 
 #[test]
@@ -666,8 +665,8 @@ fn test_find_cheapest_path_unknown_target() {
 #[test]
 fn test_reduction_edge_struct() {
     let edge = ReductionEdge {
-        source_variant: &[("graph", "PlanarGraph"), ("weight", "Unweighted")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        source_variant: vec![("graph", "PlanarGraph"), ("weight", "Unweighted")],
+        target_variant: vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
         overhead: ReductionOverhead::default(),
     };
 
@@ -679,8 +678,8 @@ fn test_reduction_edge_struct() {
 fn test_reduction_edge_default_graph() {
     // When no "graph" key is present, default to SimpleGraph
     let edge = ReductionEdge {
-        source_variant: &[("weight", "Unweighted")],
-        target_variant: &[],
+        source_variant: vec![("weight", "Unweighted")],
+        target_variant: vec![],
         overhead: ReductionOverhead::default(),
     };
 
diff --git a/src/unit_tests/rules/maximumindependentset_ilp.rs b/src/unit_tests/rules/maximumindependentset_ilp.rs
index 7d12ba13a..84e9d255a 100644
--- a/src/unit_tests/rules/maximumindependentset_ilp.rs
+++ b/src/unit_tests/rules/maximumindependentset_ilp.rs
@@ -71,7 +71,10 @@ fn test_ilp_solution_equals_brute_force_triangle() {
     assert_eq!(ilp_size, 1);
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted).is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/maximummatching_ilp.rs b/src/unit_tests/rules/maximummatching_ilp.rs
index f5464c805..c886fbea0 100644
--- a/src/unit_tests/rules/maximummatching_ilp.rs
+++ b/src/unit_tests/rules/maximummatching_ilp.rs
@@ -70,7 +70,10 @@ fn test_ilp_solution_equals_brute_force_triangle() {
     assert_eq!(ilp_size, SolutionSize::Valid(1));
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted).is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs
index 8e80f51af..173a02c51 100644
--- a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs
+++ b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs
@@ -84,8 +84,14 @@ fn test_matching_to_setpacking_weighted() {
 
     // Verify through direct MaximumMatching solution
     let direct_solutions = solver.find_all_best(&matching);
-    assert_eq!(matching.evaluate(&sp_solutions[0]), SolutionSize::Valid(100));
-    assert_eq!(matching.evaluate(&direct_solutions[0]), SolutionSize::Valid(100));
+    assert_eq!(
+        matching.evaluate(&sp_solutions[0]),
+        SolutionSize::Valid(100)
+    );
+    assert_eq!(
+        matching.evaluate(&direct_solutions[0]),
+        SolutionSize::Valid(100)
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/maximumsetpacking_ilp.rs b/src/unit_tests/rules/maximumsetpacking_ilp.rs
index 55991fde0..a190e03d2 100644
--- a/src/unit_tests/rules/maximumsetpacking_ilp.rs
+++ b/src/unit_tests/rules/maximumsetpacking_ilp.rs
@@ -70,7 +70,10 @@ fn test_ilp_solution_equals_brute_force_chain() {
     assert_eq!(ilp_size, 2);
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted).is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/minimumdominatingset_ilp.rs b/src/unit_tests/rules/minimumdominatingset_ilp.rs
index 094e82f50..40a348254 100644
--- a/src/unit_tests/rules/minimumdominatingset_ilp.rs
+++ b/src/unit_tests/rules/minimumdominatingset_ilp.rs
@@ -72,7 +72,10 @@ fn test_ilp_solution_equals_brute_force_star() {
     assert_eq!(ilp_size, SolutionSize::Valid(1));
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted) .is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
@@ -99,7 +102,7 @@ fn test_ilp_solution_equals_brute_force_path() {
     assert_eq!(ilp_size, SolutionSize::Valid(2));
 
     // Verify validity
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
 }
 
 #[test]
@@ -139,7 +142,7 @@ fn test_solution_extraction() {
     assert_eq!(extracted, vec![1, 0, 1, 0]);
 
     // Verify this is a valid DS (0 dominates 0,1 and 2 dominates 2,3)
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
 }
 
 #[test]
@@ -167,7 +170,7 @@ fn test_isolated_vertices() {
     // Vertex 2 must be selected (isolated)
     assert_eq!(extracted[2], 1);
 
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
 }
 
 #[test]
@@ -184,7 +187,7 @@ fn test_complete_graph() {
     let ilp_solution = ilp_solver.solve(ilp).expect("ILP should be solvable");
     let extracted = reduction.extract_solution(&ilp_solution);
 
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
     assert_eq!(problem.evaluate(&extracted), SolutionSize::Valid(1));
 }
 
@@ -201,7 +204,7 @@ fn test_single_vertex() {
 
     assert_eq!(extracted, vec![1]);
 
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
     assert_eq!(problem.evaluate(&extracted), SolutionSize::Valid(1));
 }
 
@@ -228,5 +231,5 @@ fn test_cycle_graph() {
 
     assert_eq!(bf_size, ilp_size);
 
-    assert!(problem.evaluate(&extracted) .is_valid());
+    assert!(problem.evaluate(&extracted).is_valid());
 }
diff --git a/src/unit_tests/rules/minimumsetcovering_ilp.rs b/src/unit_tests/rules/minimumsetcovering_ilp.rs
index 67faac88b..f586c8cb4 100644
--- a/src/unit_tests/rules/minimumsetcovering_ilp.rs
+++ b/src/unit_tests/rules/minimumsetcovering_ilp.rs
@@ -70,7 +70,10 @@ fn test_ilp_solution_equals_brute_force_simple() {
     assert_eq!(ilp_size, 2);
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted).is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/minimumvertexcover_ilp.rs b/src/unit_tests/rules/minimumvertexcover_ilp.rs
index 4fa5d7ce5..0523abb90 100644
--- a/src/unit_tests/rules/minimumvertexcover_ilp.rs
+++ b/src/unit_tests/rules/minimumvertexcover_ilp.rs
@@ -71,7 +71,10 @@ fn test_ilp_solution_equals_brute_force_triangle() {
     assert_eq!(ilp_size, 2);
 
     // Verify the ILP solution is valid for the original problem
-    assert!(problem.evaluate(&extracted).is_valid(), "Extracted solution should be valid");
+    assert!(
+        problem.evaluate(&extracted).is_valid(),
+        "Extracted solution should be valid"
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs
index def0f1398..4af15ba5f 100644
--- a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs
+++ b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs
@@ -168,7 +168,7 @@ fn test_vc_to_sc_all_solutions_valid() {
         let vc_sol = reduction.extract_solution(sc_sol);
         let eval = vc_problem.evaluate(&vc_sol);
         assert!(
-            eval .is_valid(),
+            eval.is_valid(),
             "Extracted solution {:?} should be valid",
             vc_sol
         );
diff --git a/src/unit_tests/rules/registry.rs b/src/unit_tests/rules/registry.rs
index 64fd088d1..99519935f 100644
--- a/src/unit_tests/rules/registry.rs
+++ b/src/unit_tests/rules/registry.rs
@@ -23,8 +23,8 @@ fn test_reduction_entry_overhead() {
     let entry = ReductionEntry {
         source_name: "TestSource",
         target_name: "TestTarget",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
         overhead_fn: || ReductionOverhead::new(vec![("n", poly!(2 * n))]),
         module_path: "test::module",
     };
@@ -40,8 +40,8 @@ fn test_reduction_entry_debug() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
@@ -56,8 +56,8 @@ fn test_is_base_reduction_unweighted() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
@@ -69,8 +69,8 @@ fn test_is_base_reduction_source_weighted() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "i32")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "i32")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
@@ -82,8 +82,8 @@ fn test_is_base_reduction_target_weighted() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "Unweighted")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "f64")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "Unweighted")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "f64")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
@@ -95,8 +95,8 @@ fn test_is_base_reduction_both_weighted() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph"), ("weight", "i32")],
-        target_variant: &[("graph", "SimpleGraph"), ("weight", "f64")],
+        source_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "i32")],
+        target_variant_fn: || vec![("graph", "SimpleGraph"), ("weight", "f64")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
@@ -109,8 +109,8 @@ fn test_is_base_reduction_no_weight_key() {
     let entry = ReductionEntry {
         source_name: "A",
         target_name: "B",
-        source_variant: &[("graph", "SimpleGraph")],
-        target_variant: &[("graph", "SimpleGraph")],
+        source_variant_fn: || vec![("graph", "SimpleGraph")],
+        target_variant_fn: || vec![("graph", "SimpleGraph")],
         overhead_fn: || ReductionOverhead::default(),
         module_path: "test::module",
     };
diff --git a/src/unit_tests/rules/sat_coloring.rs b/src/unit_tests/rules/sat_coloring.rs
index c1dac85af..879724235 100644
--- a/src/unit_tests/rules/sat_coloring.rs
+++ b/src/unit_tests/rules/sat_coloring.rs
@@ -32,7 +32,7 @@ fn test_special_vertex_accessors() {
 fn test_simple_sat_to_coloring() {
     // Simple SAT: (x1) - one clause with one literal
     let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1])]);
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Should have 2*1 + 3 = 5 base vertices
@@ -49,7 +49,7 @@ fn test_reduction_structure() {
         vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 2])],
     );
 
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Base vertices: 3 (TRUE, FALSE, AUX) + 2*2 (pos and neg for each var) = 7
@@ -64,10 +64,9 @@ fn test_reduction_structure() {
 #[test]
 fn test_unsatisfiable_formula() {
     // Unsatisfiable: (x1) AND (NOT x1)
-    let sat =
-        Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
+    let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
 
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Solve the coloring problem - use find_all_satisfying since KColoring is a satisfaction problem
@@ -104,7 +103,7 @@ fn test_three_literal_clause_structure() {
     // (x1 OR x2 OR x3)
     let sat = Satisfiability::new(3, vec![CNFClause::new(vec![1, 2, 3])]);
 
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Base vertices: 3 + 2*3 = 9
@@ -123,7 +122,7 @@ fn test_coloring_structure() {
         3,
         vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])],
     );
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Verify coloring has expected structure
@@ -135,7 +134,7 @@ fn test_coloring_structure() {
 fn test_extract_solution_basic() {
     // Simple case: one variable, one clause (x1)
     let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1])]);
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
 
     // Manually construct a valid coloring where x1 has TRUE color
     // Vertices: 0=TRUE, 1=FALSE, 2=AUX, 3=x1, 4=NOT_x1
@@ -167,7 +166,7 @@ fn test_complex_formula_structure() {
         ],
     );
 
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // Base vertices: 3 + 2*3 = 9
@@ -183,7 +182,7 @@ fn test_single_literal_clauses() {
     // (x1) AND (x2) - both must be true
     let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1]), CNFClause::new(vec![2])]);
 
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     let solver = BruteForce::new();
@@ -208,7 +207,7 @@ fn test_single_literal_clauses() {
 fn test_empty_sat() {
     // Empty SAT (trivially satisfiable)
     let sat = Satisfiability::new(0, vec![]);
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
 
     assert_eq!(reduction.num_clauses(), 0);
     assert!(reduction.pos_vertices().is_empty());
@@ -225,7 +224,7 @@ fn test_num_clauses_accessor() {
         2,
         vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1])],
     );
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     assert_eq!(reduction.num_clauses(), 2);
 }
 
@@ -252,7 +251,7 @@ fn test_manual_coloring_extraction() {
     // Test solution extraction with a manually constructed coloring solution
     // for a simple 1-variable SAT problem: (x1)
     let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1])]);
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
     let coloring = reduction.target_problem();
 
     // The graph structure for (x1) with set_true:
@@ -279,7 +278,7 @@ fn test_extraction_with_different_color_assignment() {
     // Test that extraction works with different color assignments
     // (colors may be permuted but semantics preserved)
     let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1])]);
-    let reduction = ReduceTo::<KColoring<3, SimpleGraph, i32>>::reduce_to(&sat);
+    let reduction = ReduceTo::<KColoring<3, SimpleGraph>>::reduce_to(&sat);
 
     // Different valid coloring: TRUE=2, FALSE=0, AUX=1
     // x1 must have color 2 (TRUE), NOT_x1 must have color 0 (FALSE)
diff --git a/src/unit_tests/rules/sat_ksat.rs b/src/unit_tests/rules/sat_ksat.rs
index a2a115f70..9775c061e 100644
--- a/src/unit_tests/rules/sat_ksat.rs
+++ b/src/unit_tests/rules/sat_ksat.rs
@@ -294,8 +294,7 @@ fn test_mixed_clause_sizes() {
 #[test]
 fn test_unsatisfiable_formula() {
     // (x) AND (-x) is unsatisfiable
-    let sat =
-        Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
+    let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
 
     let reduction = ReduceTo::<KSatisfiability<3>>::reduce_to(&sat);
     let ksat = reduction.target_problem();
diff --git a/src/unit_tests/rules/sat_maximumindependentset.rs b/src/unit_tests/rules/sat_maximumindependentset.rs
index 5a208e4ad..aa6e6f240 100644
--- a/src/unit_tests/rules/sat_maximumindependentset.rs
+++ b/src/unit_tests/rules/sat_maximumindependentset.rs
@@ -55,8 +55,7 @@ fn test_simple_sat_to_is() {
 fn test_two_clause_sat_to_is() {
     // SAT: (x1) AND (NOT x1)
     // This is unsatisfiable
-    let sat =
-        Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
+    let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
     let reduction = ReduceTo::<MaximumIndependentSet<SimpleGraph, i32>>::reduce_to(&sat);
     let is_problem = reduction.target_problem();
 
@@ -124,8 +123,7 @@ fn test_satisfiable_formula() {
 #[test]
 fn test_unsatisfiable_formula() {
     // SAT: (x1) AND (NOT x1) - unsatisfiable
-    let sat =
-        Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
+    let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
     let reduction = ReduceTo::<MaximumIndependentSet<SimpleGraph, i32>>::reduce_to(&sat);
     let is_problem = reduction.target_problem();
 
diff --git a/src/unit_tests/rules/sat_minimumdominatingset.rs b/src/unit_tests/rules/sat_minimumdominatingset.rs
index 79fa7b5d6..b7824a3dd 100644
--- a/src/unit_tests/rules/sat_minimumdominatingset.rs
+++ b/src/unit_tests/rules/sat_minimumdominatingset.rs
@@ -73,8 +73,7 @@ fn test_satisfiable_formula() {
 #[test]
 fn test_unsatisfiable_formula() {
     // SAT: (x1) AND (NOT x1) - unsatisfiable
-    let sat =
-        Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
+    let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])]);
     let reduction = ReduceTo::<MinimumDominatingSet<SimpleGraph, i32>>::reduce_to(&sat);
     let ds_problem = reduction.target_problem();
 
diff --git a/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs b/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs
index c4196b930..af282a73c 100644
--- a/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs
+++ b/src/unit_tests/rules/unitdiskmapping/pathdecomposition.rs
@@ -248,8 +248,17 @@ fn test_ground_truth_vertex_order_is_valid_permutation() {
     let entries = load_pathwidth_ground_truth();
     for entry in &entries {
         let (n, edges) = crate::topology::smallgraph(&entry.graph).unwrap();
-        assert_eq!(n, entry.num_vertices, "{}: vertex count mismatch", entry.graph);
-        assert_eq!(edges.len(), entry.num_edges, "{}: edge count mismatch", entry.graph);
+        assert_eq!(
+            n, entry.num_vertices,
+            "{}: vertex count mismatch",
+            entry.graph
+        );
+        assert_eq!(
+            edges.len(),
+            entry.num_edges,
+            "{}: edge count mismatch",
+            entry.graph
+        );
 
         // vertex_order must be a permutation of 0..n
         let mut sorted = entry.vertex_order.clone();
@@ -292,23 +301,31 @@ fn test_branch_and_bound_matches_ground_truth() {
 
         // Must produce a complete layout
         assert_eq!(
-            layout.vertices.len(), n,
-            "{}: layout missing vertices", entry.graph
+            layout.vertices.len(),
+            n,
+            "{}: layout missing vertices",
+            entry.graph
         );
 
         // Pathwidth must match ground truth (branch-and-bound is exact)
         assert_eq!(
-            layout.vsep(), entry.pathwidth,
+            layout.vsep(),
+            entry.pathwidth,
             "{}: branch_and_bound vsep ({}) != ground truth ({})",
-            entry.graph, layout.vsep(), entry.pathwidth
+            entry.graph,
+            layout.vsep(),
+            entry.pathwidth
         );
 
         // Independently verify the produced layout's vsep
         let verified = verify_vsep(n, &edges, &layout.vertices);
         assert_eq!(
-            verified, layout.vsep(),
+            verified,
+            layout.vsep(),
             "{}: Layout.vsep ({}) != independently verified vsep ({})",
-            entry.graph, layout.vsep(), verified
+            entry.graph,
+            layout.vsep(),
+            verified
         );
     }
 }
@@ -335,18 +352,28 @@ fn test_greedy_respects_ground_truth_upper_bound() {
         assert!(
             layout.vsep() >= entry.pathwidth,
             "{}: greedy vsep ({}) < optimal ({}), which is impossible",
-            entry.graph, layout.vsep(), entry.pathwidth
+            entry.graph,
+            layout.vsep(),
+            entry.pathwidth
         );
 
         // Must be a complete layout
-        assert_eq!(layout.vertices.len(), n, "{}: greedy layout incomplete", entry.graph);
+        assert_eq!(
+            layout.vertices.len(),
+            n,
+            "{}: greedy layout incomplete",
+            entry.graph
+        );
 
         // Independently verify
         let verified = verify_vsep(n, &edges, &layout.vertices);
         assert_eq!(
-            verified, layout.vsep(),
+            verified,
+            layout.vsep(),
             "{}: greedy Layout.vsep ({}) != independently verified ({})",
-            entry.graph, layout.vsep(), verified
+            entry.graph,
+            layout.vsep(),
+            verified
         );
     }
 }
diff --git a/src/unit_tests/solvers/brute_force.rs b/src/unit_tests/solvers/brute_force.rs
index fa78cd1d1..7ce9f718d 100644
--- a/src/unit_tests/solvers/brute_force.rs
+++ b/src/unit_tests/solvers/brute_force.rs
@@ -200,7 +200,10 @@ fn test_solver_find_satisfying_empty_dims_unsat() {
     let solver = BruteForce::new();
 
     assert_eq!(solver.find_satisfying(&problem), None);
-    assert_eq!(solver.find_all_satisfying(&problem), Vec::<Vec<usize>>::new());
+    assert_eq!(
+        solver.find_all_satisfying(&problem),
+        Vec::<Vec<usize>>::new()
+    );
 }
 
 #[test]
diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs
index 26c7b978e..df3452a29 100644
--- a/src/unit_tests/trait_consistency.rs
+++ b/src/unit_tests/trait_consistency.rs
@@ -37,7 +37,7 @@ fn test_all_problems_implement_trait_correctly() {
         "MaxCut",
     );
     check_problem_trait(
-        &KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1)]),
+        &KColoring::<3, SimpleGraph>::new(3, vec![(0, 1)]),
         "KColoring",
     );
     check_problem_trait(
@@ -78,7 +78,7 @@ fn test_all_problems_implement_trait_correctly() {
         vec!["x".to_string()],
         BooleanExpr::constant(true),
     )]);
-    check_problem_trait(&CircuitSAT::<i32>::new(circuit), "CircuitSAT");
+    check_problem_trait(&CircuitSAT::new(circuit), "CircuitSAT");
 }
 
 #[test]
@@ -111,7 +111,10 @@ fn test_direction() {
         SpinGlass::new(1, vec![], vec![0.0]).direction(),
         Direction::Minimize
     );
-    assert_eq!(BMF::new(vec![vec![true]], 1).direction(), Direction::Minimize);
+    assert_eq!(
+        BMF::new(vec![vec![true]], 1).direction(),
+        Direction::Minimize
+    );
     assert_eq!(Factoring::new(6, 2, 2).direction(), Direction::Minimize);
     assert_eq!(
         BicliqueCover::new(2, 2, vec![(0, 2)], 1).direction(),
diff --git a/src/unit_tests/variant.rs b/src/unit_tests/variant.rs
index c8dbff4d0..6ffc2608f 100644
--- a/src/unit_tests/variant.rs
+++ b/src/unit_tests/variant.rs
@@ -74,12 +74,11 @@ fn test_variant_for_problems() {
 
     // Note: f64 variants removed because SolutionSize now requires Ord
 
-    // Test KColoring (has K, graph, and weight parameters)
-    let v = KColoring::<3, SimpleGraph, i32>::variant();
-    assert_eq!(v.len(), 3);
+    // Test KColoring (has K and graph parameters)
+    let v = KColoring::<3, SimpleGraph>::variant();
+    assert_eq!(v.len(), 2);
     assert_eq!(v[0], ("k", "3"));
     assert_eq!(v[1], ("graph", "SimpleGraph"));
-    assert_eq!(v[2], ("weight", "i32"));
 
     // Test MaximalIS
     let v = MaximalIS::<SimpleGraph, i32>::variant();
@@ -121,8 +120,8 @@ fn test_variant_for_problems() {
     assert_eq!(v[1].1, "f64");
 
     // Test CircuitSAT
-    let v = CircuitSAT::<i32>::variant();
-    assert_eq!(v.len(), 2);
+    let v = CircuitSAT::variant();
+    assert_eq!(v.len(), 1);
 
     // Test Factoring (no type parameters)
     let v = Factoring::variant();
diff --git a/tests/suites/examples.rs b/tests/suites/examples.rs
index 24a8d363c..0a394853e 100644
--- a/tests/suites/examples.rs
+++ b/tests/suites/examples.rs
@@ -50,33 +50,96 @@ macro_rules! example_fn {
     };
 }
 
-example_fn!(test_circuitsat_to_spinglass, reduction_circuitsat_to_spinglass);
-example_fn!(test_factoring_to_circuitsat, reduction_factoring_to_circuitsat);
+example_fn!(
+    test_circuitsat_to_spinglass,
+    reduction_circuitsat_to_spinglass
+);
+example_fn!(
+    test_factoring_to_circuitsat,
+    reduction_factoring_to_circuitsat
+);
 example_fn!(test_factoring_to_ilp, reduction_factoring_to_ilp);
 example_fn!(test_ilp_to_qubo, reduction_ilp_to_qubo);
 example_fn!(test_kcoloring_to_ilp, reduction_kcoloring_to_ilp);
 example_fn!(test_kcoloring_to_qubo, reduction_kcoloring_to_qubo);
-example_fn!(test_ksatisfiability_to_qubo, reduction_ksatisfiability_to_qubo);
+example_fn!(
+    test_ksatisfiability_to_qubo,
+    reduction_ksatisfiability_to_qubo
+);
 example_fn!(test_maxcut_to_spinglass, reduction_maxcut_to_spinglass);
 example_fn!(test_maximumclique_to_ilp, reduction_maximumclique_to_ilp);
-example_fn!(test_maximumindependentset_to_ilp, reduction_maximumindependentset_to_ilp);
-example_fn!(test_maximumindependentset_to_maximumsetpacking, reduction_maximumindependentset_to_maximumsetpacking);
-example_fn!(test_maximumindependentset_to_minimumvertexcover, reduction_maximumindependentset_to_minimumvertexcover);
-example_fn!(test_maximumindependentset_to_qubo, reduction_maximumindependentset_to_qubo);
-example_fn!(test_maximummatching_to_ilp, reduction_maximummatching_to_ilp);
-example_fn!(test_maximummatching_to_maximumsetpacking, reduction_maximummatching_to_maximumsetpacking);
-example_fn!(test_maximumsetpacking_to_ilp, reduction_maximumsetpacking_to_ilp);
-example_fn!(test_maximumsetpacking_to_qubo, reduction_maximumsetpacking_to_qubo);
-example_fn!(test_minimumdominatingset_to_ilp, reduction_minimumdominatingset_to_ilp);
-example_fn!(test_minimumsetcovering_to_ilp, reduction_minimumsetcovering_to_ilp);
-example_fn!(test_minimumvertexcover_to_ilp, reduction_minimumvertexcover_to_ilp);
-example_fn!(test_minimumvertexcover_to_maximumindependentset, reduction_minimumvertexcover_to_maximumindependentset);
-example_fn!(test_minimumvertexcover_to_minimumsetcovering, reduction_minimumvertexcover_to_minimumsetcovering);
-example_fn!(test_minimumvertexcover_to_qubo, reduction_minimumvertexcover_to_qubo);
+example_fn!(
+    test_maximumindependentset_to_ilp,
+    reduction_maximumindependentset_to_ilp
+);
+example_fn!(
+    test_maximumindependentset_to_maximumsetpacking,
+    reduction_maximumindependentset_to_maximumsetpacking
+);
+example_fn!(
+    test_maximumindependentset_to_minimumvertexcover,
+    reduction_maximumindependentset_to_minimumvertexcover
+);
+example_fn!(
+    test_maximumindependentset_to_qubo,
+    reduction_maximumindependentset_to_qubo
+);
+example_fn!(
+    test_maximummatching_to_ilp,
+    reduction_maximummatching_to_ilp
+);
+example_fn!(
+    test_maximummatching_to_maximumsetpacking,
+    reduction_maximummatching_to_maximumsetpacking
+);
+example_fn!(
+    test_maximumsetpacking_to_ilp,
+    reduction_maximumsetpacking_to_ilp
+);
+example_fn!(
+    test_maximumsetpacking_to_qubo,
+    reduction_maximumsetpacking_to_qubo
+);
+example_fn!(
+    test_minimumdominatingset_to_ilp,
+    reduction_minimumdominatingset_to_ilp
+);
+example_fn!(
+    test_minimumsetcovering_to_ilp,
+    reduction_minimumsetcovering_to_ilp
+);
+example_fn!(
+    test_minimumvertexcover_to_ilp,
+    reduction_minimumvertexcover_to_ilp
+);
+example_fn!(
+    test_minimumvertexcover_to_maximumindependentset,
+    reduction_minimumvertexcover_to_maximumindependentset
+);
+example_fn!(
+    test_minimumvertexcover_to_minimumsetcovering,
+    reduction_minimumvertexcover_to_minimumsetcovering
+);
+example_fn!(
+    test_minimumvertexcover_to_qubo,
+    reduction_minimumvertexcover_to_qubo
+);
 example_fn!(test_qubo_to_spinglass, reduction_qubo_to_spinglass);
-example_fn!(test_satisfiability_to_kcoloring, reduction_satisfiability_to_kcoloring);
-example_fn!(test_satisfiability_to_ksatisfiability, reduction_satisfiability_to_ksatisfiability);
-example_fn!(test_satisfiability_to_maximumindependentset, reduction_satisfiability_to_maximumindependentset);
-example_fn!(test_satisfiability_to_minimumdominatingset, reduction_satisfiability_to_minimumdominatingset);
+example_fn!(
+    test_satisfiability_to_kcoloring,
+    reduction_satisfiability_to_kcoloring
+);
+example_fn!(
+    test_satisfiability_to_ksatisfiability,
+    reduction_satisfiability_to_ksatisfiability
+);
+example_fn!(
+    test_satisfiability_to_maximumindependentset,
+    reduction_satisfiability_to_maximumindependentset
+);
+example_fn!(
+    test_satisfiability_to_minimumdominatingset,
+    reduction_satisfiability_to_minimumdominatingset
+);
 example_fn!(test_spinglass_to_maxcut, reduction_spinglass_to_maxcut);
 example_fn!(test_spinglass_to_qubo, reduction_spinglass_to_qubo);
diff --git a/tests/suites/integration.rs b/tests/suites/integration.rs
index 90bfd0b0d..b77218095 100644
--- a/tests/suites/integration.rs
+++ b/tests/suites/integration.rs
@@ -48,7 +48,7 @@ mod all_problems_solvable {
 
     #[test]
     fn test_coloring_solvable() {
-        let problem = KColoring::<3, SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
+        let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]);
         let solver = BruteForce::new();
         // KColoring returns bool, so we can use find_all_satisfying
         let satisfying = solver.find_all_satisfying(&problem);
@@ -101,7 +101,8 @@ mod all_problems_solvable {
         );
         // Satisfiability returns bool, find satisfying configs manually
         let dims = problem.dims();
-        let all_configs: Vec<Vec<usize>> = problemreductions::config::DimsIterator::new(dims.clone()).collect();
+        let all_configs: Vec<Vec<usize>> =
+            problemreductions::config::DimsIterator::new(dims.clone()).collect();
         let satisfying: Vec<Vec<usize>> = all_configs
             .into_iter()
             .filter(|config| problem.evaluate(config))
@@ -162,10 +163,11 @@ mod all_problems_solvable {
             vec!["c".to_string()],
             BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
         )]);
-        let problem = CircuitSAT::<i32>::new(circuit);
+        let problem = CircuitSAT::new(circuit);
         // CircuitSAT returns bool
         let dims = problem.dims();
-        let all_configs: Vec<Vec<usize>> = problemreductions::config::DimsIterator::new(dims.clone()).collect();
+        let all_configs: Vec<Vec<usize>> =
+            problemreductions::config::DimsIterator::new(dims.clone()).collect();
         let satisfying: Vec<Vec<usize>> = all_configs
             .into_iter()
             .filter(|config| problem.evaluate(config))
@@ -355,7 +357,8 @@ mod edge_cases {
         let problem = Satisfiability::new(2, vec![CNFClause::new(vec![1, -2])]);
         // Find satisfying configs
         let dims = problem.dims();
-        let all_configs: Vec<Vec<usize>> = problemreductions::config::DimsIterator::new(dims.clone()).collect();
+        let all_configs: Vec<Vec<usize>> =
+            problemreductions::config::DimsIterator::new(dims.clone()).collect();
         let satisfying: Vec<Vec<usize>> = all_configs
             .into_iter()
             .filter(|config| problem.evaluate(config))
@@ -456,7 +459,8 @@ mod weighted_problems {
 
         // Find satisfying configs
         let dims = problem.dims();
-        let all_configs: Vec<Vec<usize>> = problemreductions::config::DimsIterator::new(dims.clone()).collect();
+        let all_configs: Vec<Vec<usize>> =
+            problemreductions::config::DimsIterator::new(dims.clone()).collect();
         let satisfying: Vec<Vec<usize>> = all_configs
             .into_iter()
             .filter(|config| problem.evaluate(config))
diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs
index a028f294d..766a4b5ef 100644
--- a/tests/suites/reductions.rs
+++ b/tests/suites/reductions.rs
@@ -32,7 +32,7 @@ mod is_vc_reductions {
         let is_solution = result.extract_solution(&vc_solutions[0]);
 
         // Solution should be valid for original problem
-        assert!(is_problem.evaluate(&is_solution) .is_valid());
+        assert!(is_problem.evaluate(&is_solution).is_valid());
     }
 
     #[test]
@@ -57,7 +57,7 @@ mod is_vc_reductions {
         let vc_solution = result.extract_solution(&is_solutions[0]);
 
         // Solution should be valid for original problem
-        assert!(vc_problem.evaluate(&vc_solution) .is_valid());
+        assert!(vc_problem.evaluate(&vc_solution).is_valid());
     }
 
     #[test]
@@ -86,7 +86,7 @@ mod is_vc_reductions {
         let original_sol = to_vc.extract_solution(&intermediate_sol);
 
         // Should be valid
-        assert!(original.evaluate(&original_sol) .is_valid());
+        assert!(original.evaluate(&original_sol).is_valid());
     }
 
     #[test]
@@ -145,7 +145,7 @@ mod is_sp_reductions {
         // Extract to IS solution
         let is_solution = result.extract_solution(&sp_solutions[0]);
 
-        assert!(is_problem.evaluate(&is_solution) .is_valid());
+        assert!(is_problem.evaluate(&is_solution).is_valid());
     }
 
     #[test]
@@ -189,7 +189,7 @@ mod is_sp_reductions {
         let is_solution = to_sp.extract_solution(&sp_solutions[0]);
 
         // Valid for original
-        assert!(original.evaluate(&is_solution) .is_valid());
+        assert!(original.evaluate(&is_solution).is_valid());
 
         // Should match directly solving IS
         let direct_solutions = solver.find_all_best(&original);
@@ -496,7 +496,7 @@ mod qubo_reductions {
         // All QUBO optimal solutions should extract to valid IS solutions
         for sol in &solutions {
             let extracted = reduction.extract_solution(sol);
-            assert!(is.evaluate(&extracted) .is_valid());
+            assert!(is.evaluate(&extracted).is_valid());
         }
 
         // Optimal IS size should match ground truth
@@ -538,7 +538,7 @@ mod qubo_reductions {
 
         for sol in &solutions {
             let extracted = reduction.extract_solution(sol);
-            assert!(vc.evaluate(&extracted) .is_valid());
+            assert!(vc.evaluate(&extracted).is_valid());
         }
 
         // Optimal VC size should match ground truth
@@ -568,7 +568,7 @@ mod qubo_reductions {
 
         assert_eq!(data.source.num_colors, 3);
 
-        let kc = KColoring::<3, SimpleGraph, i32>::new(data.source.num_vertices, data.source.edges);
+        let kc = KColoring::<3, SimpleGraph>::new(data.source.num_vertices, data.source.edges);
         let reduction = ReduceTo::<QUBO>::reduce_to(&kc);
         let qubo = reduction.target_problem();
 
