diff --git a/benches/solver_benchmarks.rs b/benches/solver_benchmarks.rs index 8d3589a8..78ccf75d 100644 --- a/benches/solver_benchmarks.rs +++ b/benches/solver_benchmarks.rs @@ -8,6 +8,7 @@ use problemreductions::models::set::*; use problemreductions::models::specialized::*; use problemreductions::prelude::*; use problemreductions::topology::SimpleGraph; +use problemreductions::variant::K3; use std::hint::black_box; /// Benchmark MaximumIndependentSet on graphs of varying sizes. @@ -138,7 +139,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>::new(*n, edges); + let problem = KColoring::::new(*n, edges); let solver = BruteForce::new(); group.bench_with_input(BenchmarkId::new("path_3colors", n), n, |b, _| { diff --git a/book.toml b/book.toml index 13a8e3fd..d49b7a9b 100644 --- a/book.toml +++ b/book.toml @@ -9,8 +9,8 @@ src = "docs/src" default-theme = "navy" git-repository-url = "https://github.com/CodingThrust/problem-reductions" edit-url-template = "https://github.com/CodingThrust/problem-reductions/edit/main/{path}" -additional-css = ["docs/src/static/theme-images.css"] -additional-js = [] +additional-css = ["docs/src/static/theme-images.css", "docs/src/static/reduction-graph.css"] +additional-js = ["docs/src/static/cytoscape.min.js", "docs/src/static/reduction-graph.js"] no-section-label = false [output.html.fold] diff --git a/docs/paper/lib.typ b/docs/paper/lib.typ index 9de77269..8652d4f6 100644 --- a/docs/paper/lib.typ +++ b/docs/paper/lib.typ @@ -93,10 +93,9 @@ // 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))) + let positions = data.nodes.map(n => (n.col * cell-size, -n.row * cell-size)) + let fills = data.nodes.map(n => weight-color(n.weight)) + let edges = 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)) @@ -104,16 +103,15 @@ }) // Triangular lattice from JSON with weight-based coloring -// Matches Rust GridGraph::physical_position_static for Triangular (offset_even_cols=true) +// Physical positions use triangular lattice transform (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 positions = 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))) + let fills = data.nodes.map(n => weight-color(n.weight)) + let edges = 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 d163e99b..5bc4d7f1 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -43,8 +43,8 @@ "KSatisfiability": [$k$-SAT], "CircuitSAT": [CircuitSAT], "Factoring": [Factoring], - "GridGraph": [GridGraph MIS], - "Triangular": [Triangular MIS], + "KingsSubgraph": [King's Subgraph MIS], + "TriangularSubgraph": [Triangular Subgraph MIS], ) // Definition label: "def:" — each definition block must have a matching label @@ -828,7 +828,7 @@ The following reductions to Integer Linear Programming are straightforward formu == Unit Disk Mapping -#reduction-rule("MaximumIndependentSet", "GridGraph")[ +#reduction-rule("MaximumIndependentSet", "KingsSubgraph")[ @nguyen2023 Any MIS problem on a general graph $G$ can be reduced to MIS on a unit disk graph (King's subgraph) with at most quadratic overhead in the number of vertices. ][ _Construction (Copy-Line Method)._ Given $G = (V, E)$ with $n = |V|$: @@ -885,7 +885,7 @@ The following reductions to Integer Linear Programming are straightforward formu caption: [Unit disk mappings of the Petersen graph. Blue: weight 1, red: weight 2, green: weight 3.], ) -#reduction-rule("MaximumIndependentSet", "Triangular")[ +#reduction-rule("MaximumIndependentSet", "TriangularSubgraph")[ @nguyen2023 Any MIS problem on a general graph $G$ can be reduced to MIS on a weighted triangular lattice graph with at most quadratic overhead in the number of vertices. ][ _Construction._ Same copy-line method as the KSG mapping, but uses a triangular lattice instead of a square grid. Crossing and simplifier gadgets are adapted for triangular geometry, producing a unit disk graph on a triangular grid where edges connect nodes within unit distance under the triangular metric. diff --git a/docs/paper/static/petersen_source.json b/docs/paper/static/petersen_source.json index d6029bfa..fb55e615 100644 --- a/docs/paper/static/petersen_source.json +++ b/docs/paper/static/petersen_source.json @@ -1,67 +1 @@ -{ - "name": "Petersen", - "num_vertices": 10, - "edges": [ - [ - 0, - 1 - ], - [ - 1, - 2 - ], - [ - 2, - 3 - ], - [ - 3, - 4 - ], - [ - 4, - 0 - ], - [ - 5, - 7 - ], - [ - 7, - 9 - ], - [ - 9, - 6 - ], - [ - 6, - 8 - ], - [ - 8, - 5 - ], - [ - 0, - 5 - ], - [ - 1, - 6 - ], - [ - 2, - 7 - ], - [ - 3, - 8 - ], - [ - 4, - 9 - ] - ], - "mis": 4 -} \ No newline at end of file +{"name":"Petersen","num_vertices":10,"edges":[[0,1],[1,2],[2,3],[3,4],[4,0],[5,7],[7,9],[9,6],[6,8],[8,5],[0,5],[1,6],[2,7],[3,8],[4,9]],"mis":4} \ No newline at end of file diff --git a/docs/paper/static/petersen_square_unweighted.json b/docs/paper/static/petersen_square_unweighted.json index 5d2fdc68..10f0f52f 100644 --- a/docs/paper/static/petersen_square_unweighted.json +++ b/docs/paper/static/petersen_square_unweighted.json @@ -1,2581 +1 @@ -{ - "grid_graph": { - "grid_type": "Square", - "size": [ - 30, - 42 - ], - "nodes": [ - { - "row": 2, - "col": 6, - "weight": 1 - }, - { - "row": 2, - "col": 18, - "weight": 1 - }, - { - "row": 2, - "col": 34, - "weight": 1 - }, - { - "row": 3, - "col": 5, - "weight": 1 - }, - { - "row": 3, - "col": 7, - "weight": 1 - }, - { - "row": 3, - "col": 8, - "weight": 2 - }, - { - "row": 3, - "col": 9, - "weight": 2 - }, - { - "row": 3, - "col": 10, - "weight": 2 - }, - { - "row": 3, - "col": 11, - "weight": 2 - }, - { - "row": 3, - "col": 12, - "weight": 2 - }, - { - "row": 3, - "col": 13, - "weight": 2 - }, - { - "row": 3, - "col": 14, - "weight": 2 - }, - { - "row": 3, - "col": 15, - "weight": 2 - }, - { - "row": 3, - "col": 16, - "weight": 2 - }, - { - "row": 3, - "col": 17, - "weight": 1 - }, - { - "row": 3, - "col": 19, - "weight": 1 - }, - { - "row": 3, - "col": 20, - "weight": 2 - }, - { - "row": 3, - "col": 21, - "weight": 1 - }, - { - "row": 3, - "col": 28, - "weight": 1 - }, - { - "row": 3, - "col": 29, - "weight": 2 - }, - { - "row": 3, - "col": 30, - "weight": 2 - }, - { - "row": 3, - "col": 31, - "weight": 2 - }, - { - "row": 3, - "col": 32, - "weight": 2 - }, - { - "row": 3, - "col": 33, - "weight": 1 - }, - { - "row": 3, - "col": 35, - "weight": 1 - }, - { - "row": 3, - "col": 36, - "weight": 2 - }, - { - "row": 3, - "col": 37, - "weight": 1 - }, - { - "row": 4, - "col": 6, - "weight": 1 - }, - { - "row": 4, - "col": 18, - "weight": 1 - }, - { - "row": 4, - "col": 22, - "weight": 1 - }, - { - "row": 4, - "col": 27, - "weight": 1 - }, - { - "row": 4, - "col": 34, - "weight": 1 - }, - { - "row": 4, - "col": 38, - "weight": 1 - }, - { - "row": 5, - "col": 6, - "weight": 1 - }, - { - "row": 5, - "col": 18, - "weight": 2 - }, - { - "row": 5, - "col": 22, - "weight": 2 - }, - { - "row": 5, - "col": 26, - "weight": 1 - }, - { - "row": 5, - "col": 34, - "weight": 2 - }, - { - "row": 5, - "col": 38, - "weight": 2 - }, - { - "row": 6, - "col": 7, - "weight": 1 - }, - { - "row": 6, - "col": 10, - "weight": 1 - }, - { - "row": 6, - "col": 18, - "weight": 1 - }, - { - "row": 6, - "col": 22, - "weight": 1 - }, - { - "row": 6, - "col": 26, - "weight": 1 - }, - { - "row": 6, - "col": 34, - "weight": 1 - }, - { - "row": 6, - "col": 38, - "weight": 1 - }, - { - "row": 7, - "col": 8, - "weight": 1 - }, - { - "row": 7, - "col": 9, - "weight": 1 - }, - { - "row": 7, - "col": 11, - "weight": 1 - }, - { - "row": 7, - "col": 12, - "weight": 2 - }, - { - "row": 7, - "col": 13, - "weight": 2 - }, - { - "row": 7, - "col": 14, - "weight": 2 - }, - { - "row": 7, - "col": 15, - "weight": 2 - }, - { - "row": 7, - "col": 16, - "weight": 1 - }, - { - "row": 7, - "col": 17, - "weight": 1 - }, - { - "row": 7, - "col": 18, - "weight": 1 - }, - { - "row": 7, - "col": 19, - "weight": 1 - }, - { - "row": 7, - "col": 20, - "weight": 1 - }, - { - "row": 7, - "col": 21, - "weight": 1 - }, - { - "row": 7, - "col": 22, - "weight": 1 - }, - { - "row": 7, - "col": 23, - "weight": 1 - }, - { - "row": 7, - "col": 24, - "weight": 1 - }, - { - "row": 7, - "col": 25, - "weight": 1 - }, - { - "row": 7, - "col": 32, - "weight": 1 - }, - { - "row": 7, - "col": 33, - "weight": 1 - }, - { - "row": 7, - "col": 34, - "weight": 1 - }, - { - "row": 7, - "col": 35, - "weight": 1 - }, - { - "row": 7, - "col": 36, - "weight": 1 - }, - { - "row": 7, - "col": 37, - "weight": 1 - }, - { - "row": 7, - "col": 39, - "weight": 1 - }, - { - "row": 8, - "col": 10, - "weight": 1 - }, - { - "row": 8, - "col": 17, - "weight": 1 - }, - { - "row": 8, - "col": 18, - "weight": 1 - }, - { - "row": 8, - "col": 19, - "weight": 1 - }, - { - "row": 8, - "col": 21, - "weight": 1 - }, - { - "row": 8, - "col": 22, - "weight": 1 - }, - { - "row": 8, - "col": 23, - "weight": 1 - }, - { - "row": 8, - "col": 31, - "weight": 1 - }, - { - "row": 8, - "col": 33, - "weight": 1 - }, - { - "row": 8, - "col": 34, - "weight": 1 - }, - { - "row": 8, - "col": 35, - "weight": 1 - }, - { - "row": 8, - "col": 38, - "weight": 1 - }, - { - "row": 9, - "col": 10, - "weight": 1 - }, - { - "row": 9, - "col": 18, - "weight": 1 - }, - { - "row": 9, - "col": 22, - "weight": 1 - }, - { - "row": 9, - "col": 30, - "weight": 1 - }, - { - "row": 9, - "col": 34, - "weight": 1 - }, - { - "row": 9, - "col": 38, - "weight": 2 - }, - { - "row": 10, - "col": 11, - "weight": 1 - }, - { - "row": 10, - "col": 14, - "weight": 1 - }, - { - "row": 10, - "col": 18, - "weight": 1 - }, - { - "row": 10, - "col": 22, - "weight": 1 - }, - { - "row": 10, - "col": 30, - "weight": 1 - }, - { - "row": 10, - "col": 34, - "weight": 1 - }, - { - "row": 10, - "col": 38, - "weight": 1 - }, - { - "row": 11, - "col": 12, - "weight": 1 - }, - { - "row": 11, - "col": 13, - "weight": 1 - }, - { - "row": 11, - "col": 15, - "weight": 1 - }, - { - "row": 11, - "col": 16, - "weight": 1 - }, - { - "row": 11, - "col": 17, - "weight": 1 - }, - { - "row": 11, - "col": 18, - "weight": 1 - }, - { - "row": 11, - "col": 19, - "weight": 1 - }, - { - "row": 11, - "col": 20, - "weight": 1 - }, - { - "row": 11, - "col": 21, - "weight": 1 - }, - { - "row": 11, - "col": 22, - "weight": 1 - }, - { - "row": 11, - "col": 23, - "weight": 1 - }, - { - "row": 11, - "col": 24, - "weight": 1 - }, - { - "row": 11, - "col": 25, - "weight": 2 - }, - { - "row": 11, - "col": 26, - "weight": 2 - }, - { - "row": 11, - "col": 27, - "weight": 2 - }, - { - "row": 11, - "col": 28, - "weight": 2 - }, - { - "row": 11, - "col": 29, - "weight": 1 - }, - { - "row": 11, - "col": 31, - "weight": 1 - }, - { - "row": 11, - "col": 34, - "weight": 1 - }, - { - "row": 11, - "col": 38, - "weight": 1 - }, - { - "row": 12, - "col": 14, - "weight": 1 - }, - { - "row": 12, - "col": 17, - "weight": 1 - }, - { - "row": 12, - "col": 18, - "weight": 1 - }, - { - "row": 12, - "col": 19, - "weight": 1 - }, - { - "row": 12, - "col": 21, - "weight": 1 - }, - { - "row": 12, - "col": 22, - "weight": 1 - }, - { - "row": 12, - "col": 23, - "weight": 1 - }, - { - "row": 12, - "col": 30, - "weight": 1 - }, - { - "row": 12, - "col": 34, - "weight": 1 - }, - { - "row": 12, - "col": 38, - "weight": 1 - }, - { - "row": 13, - "col": 14, - "weight": 1 - }, - { - "row": 13, - "col": 18, - "weight": 1 - }, - { - "row": 13, - "col": 22, - "weight": 1 - }, - { - "row": 13, - "col": 30, - "weight": 2 - }, - { - "row": 13, - "col": 34, - "weight": 1 - }, - { - "row": 13, - "col": 38, - "weight": 1 - }, - { - "row": 14, - "col": 15, - "weight": 1 - }, - { - "row": 14, - "col": 18, - "weight": 1 - }, - { - "row": 14, - "col": 22, - "weight": 1 - }, - { - "row": 14, - "col": 30, - "weight": 1 - }, - { - "row": 14, - "col": 34, - "weight": 1 - }, - { - "row": 14, - "col": 38, - "weight": 2 - }, - { - "row": 15, - "col": 16, - "weight": 1 - }, - { - "row": 15, - "col": 17, - "weight": 1 - }, - { - "row": 15, - "col": 18, - "weight": 1 - }, - { - "row": 15, - "col": 19, - "weight": 1 - }, - { - "row": 15, - "col": 20, - "weight": 1 - }, - { - "row": 15, - "col": 21, - "weight": 1 - }, - { - "row": 15, - "col": 22, - "weight": 1 - }, - { - "row": 15, - "col": 23, - "weight": 1 - }, - { - "row": 15, - "col": 24, - "weight": 1 - }, - { - "row": 15, - "col": 25, - "weight": 2 - }, - { - "row": 15, - "col": 26, - "weight": 2 - }, - { - "row": 15, - "col": 27, - "weight": 2 - }, - { - "row": 15, - "col": 28, - "weight": 1 - }, - { - "row": 15, - "col": 29, - "weight": 1 - }, - { - "row": 15, - "col": 30, - "weight": 1 - }, - { - "row": 15, - "col": 31, - "weight": 1 - }, - { - "row": 15, - "col": 32, - "weight": 1 - }, - { - "row": 15, - "col": 33, - "weight": 1 - }, - { - "row": 15, - "col": 35, - "weight": 1 - }, - { - "row": 15, - "col": 38, - "weight": 2 - }, - { - "row": 16, - "col": 18, - "weight": 1 - }, - { - "row": 16, - "col": 21, - "weight": 1 - }, - { - "row": 16, - "col": 22, - "weight": 1 - }, - { - "row": 16, - "col": 23, - "weight": 1 - }, - { - "row": 16, - "col": 29, - "weight": 1 - }, - { - "row": 16, - "col": 30, - "weight": 1 - }, - { - "row": 16, - "col": 31, - "weight": 1 - }, - { - "row": 16, - "col": 34, - "weight": 1 - }, - { - "row": 16, - "col": 38, - "weight": 2 - }, - { - "row": 17, - "col": 18, - "weight": 1 - }, - { - "row": 17, - "col": 22, - "weight": 1 - }, - { - "row": 17, - "col": 30, - "weight": 1 - }, - { - "row": 17, - "col": 34, - "weight": 2 - }, - { - "row": 17, - "col": 38, - "weight": 2 - }, - { - "row": 18, - "col": 19, - "weight": 1 - }, - { - "row": 18, - "col": 22, - "weight": 1 - }, - { - "row": 18, - "col": 30, - "weight": 1 - }, - { - "row": 18, - "col": 34, - "weight": 1 - }, - { - "row": 18, - "col": 38, - "weight": 1 - }, - { - "row": 19, - "col": 20, - "weight": 1 - }, - { - "row": 19, - "col": 21, - "weight": 1 - }, - { - "row": 19, - "col": 22, - "weight": 1 - }, - { - "row": 19, - "col": 23, - "weight": 1 - }, - { - "row": 19, - "col": 24, - "weight": 1 - }, - { - "row": 19, - "col": 25, - "weight": 2 - }, - { - "row": 19, - "col": 26, - "weight": 2 - }, - { - "row": 19, - "col": 27, - "weight": 2 - }, - { - "row": 19, - "col": 28, - "weight": 1 - }, - { - "row": 19, - "col": 29, - "weight": 1 - }, - { - "row": 19, - "col": 30, - "weight": 1 - }, - { - "row": 19, - "col": 31, - "weight": 1 - }, - { - "row": 19, - "col": 32, - "weight": 1 - }, - { - "row": 19, - "col": 33, - "weight": 1 - }, - { - "row": 19, - "col": 34, - "weight": 1 - }, - { - "row": 19, - "col": 35, - "weight": 1 - }, - { - "row": 19, - "col": 36, - "weight": 1 - }, - { - "row": 19, - "col": 37, - "weight": 1 - }, - { - "row": 20, - "col": 21, - "weight": 1 - }, - { - "row": 20, - "col": 22, - "weight": 1 - }, - { - "row": 20, - "col": 23, - "weight": 1 - }, - { - "row": 20, - "col": 29, - "weight": 1 - }, - { - "row": 20, - "col": 30, - "weight": 1 - }, - { - "row": 20, - "col": 31, - "weight": 1 - }, - { - "row": 20, - "col": 33, - "weight": 1 - }, - { - "row": 20, - "col": 34, - "weight": 1 - }, - { - "row": 20, - "col": 35, - "weight": 1 - }, - { - "row": 21, - "col": 22, - "weight": 1 - }, - { - "row": 21, - "col": 30, - "weight": 1 - }, - { - "row": 21, - "col": 34, - "weight": 1 - }, - { - "row": 22, - "col": 23, - "weight": 1 - }, - { - "row": 22, - "col": 30, - "weight": 1 - }, - { - "row": 22, - "col": 34, - "weight": 1 - }, - { - "row": 23, - "col": 24, - "weight": 1 - }, - { - "row": 23, - "col": 25, - "weight": 2 - }, - { - "row": 23, - "col": 26, - "weight": 2 - }, - { - "row": 23, - "col": 27, - "weight": 2 - }, - { - "row": 23, - "col": 28, - "weight": 2 - }, - { - "row": 23, - "col": 29, - "weight": 1 - }, - { - "row": 23, - "col": 31, - "weight": 1 - }, - { - "row": 23, - "col": 32, - "weight": 2 - }, - { - "row": 23, - "col": 33, - "weight": 1 - }, - { - "row": 24, - "col": 30, - "weight": 1 - } - ], - "radius": 1.5, - "edges": [ - [ - 0, - 3 - ], - [ - 0, - 4 - ], - [ - 1, - 14 - ], - [ - 1, - 15 - ], - [ - 2, - 23 - ], - [ - 2, - 24 - ], - [ - 3, - 27 - ], - [ - 4, - 5 - ], - [ - 4, - 27 - ], - [ - 5, - 6 - ], - [ - 6, - 7 - ], - [ - 7, - 8 - ], - [ - 8, - 9 - ], - [ - 9, - 10 - ], - [ - 10, - 11 - ], - [ - 11, - 12 - ], - [ - 12, - 13 - ], - [ - 13, - 14 - ], - [ - 14, - 28 - ], - [ - 15, - 16 - ], - [ - 15, - 28 - ], - [ - 16, - 17 - ], - [ - 17, - 29 - ], - [ - 18, - 19 - ], - [ - 18, - 30 - ], - [ - 19, - 20 - ], - [ - 20, - 21 - ], - [ - 21, - 22 - ], - [ - 22, - 23 - ], - [ - 23, - 31 - ], - [ - 24, - 25 - ], - [ - 24, - 31 - ], - [ - 25, - 26 - ], - [ - 26, - 32 - ], - [ - 27, - 33 - ], - [ - 28, - 34 - ], - [ - 29, - 35 - ], - [ - 30, - 36 - ], - [ - 31, - 37 - ], - [ - 32, - 38 - ], - [ - 33, - 39 - ], - [ - 34, - 41 - ], - [ - 35, - 42 - ], - [ - 36, - 43 - ], - [ - 37, - 44 - ], - [ - 38, - 45 - ], - [ - 39, - 46 - ], - [ - 40, - 47 - ], - [ - 40, - 48 - ], - [ - 41, - 54 - ], - [ - 41, - 55 - ], - [ - 41, - 56 - ], - [ - 42, - 58 - ], - [ - 42, - 59 - ], - [ - 42, - 60 - ], - [ - 43, - 62 - ], - [ - 44, - 64 - ], - [ - 44, - 65 - ], - [ - 44, - 66 - ], - [ - 45, - 68 - ], - [ - 45, - 69 - ], - [ - 46, - 47 - ], - [ - 47, - 70 - ], - [ - 48, - 49 - ], - [ - 48, - 70 - ], - [ - 49, - 50 - ], - [ - 50, - 51 - ], - [ - 51, - 52 - ], - [ - 52, - 53 - ], - [ - 53, - 54 - ], - [ - 53, - 71 - ], - [ - 54, - 55 - ], - [ - 54, - 71 - ], - [ - 54, - 72 - ], - [ - 55, - 56 - ], - [ - 55, - 71 - ], - [ - 55, - 72 - ], - [ - 55, - 73 - ], - [ - 56, - 57 - ], - [ - 56, - 72 - ], - [ - 56, - 73 - ], - [ - 57, - 58 - ], - [ - 57, - 73 - ], - [ - 57, - 74 - ], - [ - 58, - 59 - ], - [ - 58, - 74 - ], - [ - 58, - 75 - ], - [ - 59, - 60 - ], - [ - 59, - 74 - ], - [ - 59, - 75 - ], - [ - 59, - 76 - ], - [ - 60, - 61 - ], - [ - 60, - 75 - ], - [ - 60, - 76 - ], - [ - 61, - 62 - ], - [ - 61, - 76 - ], - [ - 63, - 64 - ], - [ - 63, - 77 - ], - [ - 63, - 78 - ], - [ - 64, - 65 - ], - [ - 64, - 78 - ], - [ - 64, - 79 - ], - [ - 65, - 66 - ], - [ - 65, - 78 - ], - [ - 65, - 79 - ], - [ - 65, - 80 - ], - [ - 66, - 67 - ], - [ - 66, - 79 - ], - [ - 66, - 80 - ], - [ - 67, - 68 - ], - [ - 67, - 80 - ], - [ - 68, - 81 - ], - [ - 69, - 81 - ], - [ - 70, - 82 - ], - [ - 71, - 72 - ], - [ - 71, - 83 - ], - [ - 72, - 73 - ], - [ - 72, - 83 - ], - [ - 73, - 83 - ], - [ - 74, - 75 - ], - [ - 74, - 84 - ], - [ - 75, - 76 - ], - [ - 75, - 84 - ], - [ - 76, - 84 - ], - [ - 77, - 85 - ], - [ - 78, - 79 - ], - [ - 78, - 86 - ], - [ - 79, - 80 - ], - [ - 79, - 86 - ], - [ - 80, - 86 - ], - [ - 81, - 87 - ], - [ - 82, - 88 - ], - [ - 83, - 90 - ], - [ - 84, - 91 - ], - [ - 85, - 92 - ], - [ - 86, - 93 - ], - [ - 87, - 94 - ], - [ - 88, - 95 - ], - [ - 89, - 96 - ], - [ - 89, - 97 - ], - [ - 90, - 99 - ], - [ - 90, - 100 - ], - [ - 90, - 101 - ], - [ - 91, - 103 - ], - [ - 91, - 104 - ], - [ - 91, - 105 - ], - [ - 92, - 111 - ], - [ - 92, - 112 - ], - [ - 93, - 113 - ], - [ - 94, - 114 - ], - [ - 95, - 96 - ], - [ - 96, - 115 - ], - [ - 97, - 98 - ], - [ - 97, - 115 - ], - [ - 98, - 99 - ], - [ - 98, - 116 - ], - [ - 99, - 100 - ], - [ - 99, - 116 - ], - [ - 99, - 117 - ], - [ - 100, - 101 - ], - [ - 100, - 116 - ], - [ - 100, - 117 - ], - [ - 100, - 118 - ], - [ - 101, - 102 - ], - [ - 101, - 117 - ], - [ - 101, - 118 - ], - [ - 102, - 103 - ], - [ - 102, - 118 - ], - [ - 102, - 119 - ], - [ - 103, - 104 - ], - [ - 103, - 119 - ], - [ - 103, - 120 - ], - [ - 104, - 105 - ], - [ - 104, - 119 - ], - [ - 104, - 120 - ], - [ - 104, - 121 - ], - [ - 105, - 106 - ], - [ - 105, - 120 - ], - [ - 105, - 121 - ], - [ - 106, - 107 - ], - [ - 106, - 121 - ], - [ - 107, - 108 - ], - [ - 108, - 109 - ], - [ - 109, - 110 - ], - [ - 110, - 111 - ], - [ - 111, - 122 - ], - [ - 112, - 122 - ], - [ - 113, - 123 - ], - [ - 114, - 124 - ], - [ - 115, - 125 - ], - [ - 116, - 117 - ], - [ - 116, - 126 - ], - [ - 117, - 118 - ], - [ - 117, - 126 - ], - [ - 118, - 126 - ], - [ - 119, - 120 - ], - [ - 119, - 127 - ], - [ - 120, - 121 - ], - [ - 120, - 127 - ], - [ - 121, - 127 - ], - [ - 122, - 128 - ], - [ - 123, - 129 - ], - [ - 124, - 130 - ], - [ - 125, - 131 - ], - [ - 126, - 132 - ], - [ - 127, - 133 - ], - [ - 128, - 134 - ], - [ - 129, - 135 - ], - [ - 130, - 136 - ], - [ - 131, - 137 - ], - [ - 132, - 138 - ], - [ - 132, - 139 - ], - [ - 132, - 140 - ], - [ - 133, - 142 - ], - [ - 133, - 143 - ], - [ - 133, - 144 - ], - [ - 134, - 150 - ], - [ - 134, - 151 - ], - [ - 134, - 152 - ], - [ - 135, - 154 - ], - [ - 135, - 155 - ], - [ - 136, - 156 - ], - [ - 137, - 138 - ], - [ - 138, - 139 - ], - [ - 138, - 157 - ], - [ - 139, - 140 - ], - [ - 139, - 157 - ], - [ - 140, - 141 - ], - [ - 140, - 157 - ], - [ - 141, - 142 - ], - [ - 141, - 158 - ], - [ - 142, - 143 - ], - [ - 142, - 158 - ], - [ - 142, - 159 - ], - [ - 143, - 144 - ], - [ - 143, - 158 - ], - [ - 143, - 159 - ], - [ - 143, - 160 - ], - [ - 144, - 145 - ], - [ - 144, - 159 - ], - [ - 144, - 160 - ], - [ - 145, - 146 - ], - [ - 145, - 160 - ], - [ - 146, - 147 - ], - [ - 147, - 148 - ], - [ - 148, - 149 - ], - [ - 149, - 150 - ], - [ - 149, - 161 - ], - [ - 150, - 151 - ], - [ - 150, - 161 - ], - [ - 150, - 162 - ], - [ - 151, - 152 - ], - [ - 151, - 161 - ], - [ - 151, - 162 - ], - [ - 151, - 163 - ], - [ - 152, - 153 - ], - [ - 152, - 162 - ], - [ - 152, - 163 - ], - [ - 153, - 154 - ], - [ - 153, - 163 - ], - [ - 154, - 164 - ], - [ - 155, - 164 - ], - [ - 156, - 165 - ], - [ - 157, - 166 - ], - [ - 158, - 159 - ], - [ - 158, - 167 - ], - [ - 159, - 160 - ], - [ - 159, - 167 - ], - [ - 160, - 167 - ], - [ - 161, - 162 - ], - [ - 161, - 168 - ], - [ - 162, - 163 - ], - [ - 162, - 168 - ], - [ - 163, - 168 - ], - [ - 164, - 169 - ], - [ - 165, - 170 - ], - [ - 166, - 171 - ], - [ - 167, - 172 - ], - [ - 168, - 173 - ], - [ - 169, - 174 - ], - [ - 170, - 175 - ], - [ - 171, - 176 - ], - [ - 172, - 177 - ], - [ - 172, - 178 - ], - [ - 172, - 179 - ], - [ - 173, - 185 - ], - [ - 173, - 186 - ], - [ - 173, - 187 - ], - [ - 174, - 189 - ], - [ - 174, - 190 - ], - [ - 174, - 191 - ], - [ - 175, - 193 - ], - [ - 176, - 177 - ], - [ - 176, - 194 - ], - [ - 177, - 178 - ], - [ - 177, - 194 - ], - [ - 177, - 195 - ], - [ - 178, - 179 - ], - [ - 178, - 194 - ], - [ - 178, - 195 - ], - [ - 178, - 196 - ], - [ - 179, - 180 - ], - [ - 179, - 195 - ], - [ - 179, - 196 - ], - [ - 180, - 181 - ], - [ - 180, - 196 - ], - [ - 181, - 182 - ], - [ - 182, - 183 - ], - [ - 183, - 184 - ], - [ - 184, - 185 - ], - [ - 184, - 197 - ], - [ - 185, - 186 - ], - [ - 185, - 197 - ], - [ - 185, - 198 - ], - [ - 186, - 187 - ], - [ - 186, - 197 - ], - [ - 186, - 198 - ], - [ - 186, - 199 - ], - [ - 187, - 188 - ], - [ - 187, - 198 - ], - [ - 187, - 199 - ], - [ - 188, - 189 - ], - [ - 188, - 199 - ], - [ - 188, - 200 - ], - [ - 189, - 190 - ], - [ - 189, - 200 - ], - [ - 189, - 201 - ], - [ - 190, - 191 - ], - [ - 190, - 200 - ], - [ - 190, - 201 - ], - [ - 190, - 202 - ], - [ - 191, - 192 - ], - [ - 191, - 201 - ], - [ - 191, - 202 - ], - [ - 192, - 193 - ], - [ - 192, - 202 - ], - [ - 194, - 195 - ], - [ - 194, - 203 - ], - [ - 195, - 196 - ], - [ - 195, - 203 - ], - [ - 196, - 203 - ], - [ - 197, - 198 - ], - [ - 197, - 204 - ], - [ - 198, - 199 - ], - [ - 198, - 204 - ], - [ - 199, - 204 - ], - [ - 200, - 201 - ], - [ - 200, - 205 - ], - [ - 201, - 202 - ], - [ - 201, - 205 - ], - [ - 202, - 205 - ], - [ - 203, - 206 - ], - [ - 204, - 207 - ], - [ - 205, - 208 - ], - [ - 206, - 209 - ], - [ - 207, - 214 - ], - [ - 207, - 215 - ], - [ - 208, - 217 - ], - [ - 209, - 210 - ], - [ - 210, - 211 - ], - [ - 211, - 212 - ], - [ - 212, - 213 - ], - [ - 213, - 214 - ], - [ - 214, - 218 - ], - [ - 215, - 216 - ], - [ - 215, - 218 - ], - [ - 216, - 217 - ] - ] - }, - "mis_overhead": 89, - "padding": 2, - "spacing": 4, - "weighted": false -} \ No newline at end of file +{"nodes":[{"row":2,"col":6,"weight":1},{"row":2,"col":18,"weight":1},{"row":2,"col":34,"weight":1},{"row":3,"col":5,"weight":1},{"row":3,"col":7,"weight":1},{"row":3,"col":8,"weight":2},{"row":3,"col":9,"weight":2},{"row":3,"col":10,"weight":2},{"row":3,"col":11,"weight":2},{"row":3,"col":12,"weight":2},{"row":3,"col":13,"weight":2},{"row":3,"col":14,"weight":2},{"row":3,"col":15,"weight":2},{"row":3,"col":16,"weight":2},{"row":3,"col":17,"weight":1},{"row":3,"col":19,"weight":1},{"row":3,"col":20,"weight":2},{"row":3,"col":21,"weight":1},{"row":3,"col":28,"weight":1},{"row":3,"col":29,"weight":2},{"row":3,"col":30,"weight":2},{"row":3,"col":31,"weight":2},{"row":3,"col":32,"weight":2},{"row":3,"col":33,"weight":1},{"row":3,"col":35,"weight":1},{"row":3,"col":36,"weight":2},{"row":3,"col":37,"weight":1},{"row":4,"col":6,"weight":1},{"row":4,"col":18,"weight":1},{"row":4,"col":22,"weight":1},{"row":4,"col":27,"weight":1},{"row":4,"col":34,"weight":1},{"row":4,"col":38,"weight":1},{"row":5,"col":6,"weight":1},{"row":5,"col":18,"weight":2},{"row":5,"col":22,"weight":2},{"row":5,"col":26,"weight":1},{"row":5,"col":34,"weight":2},{"row":5,"col":38,"weight":2},{"row":6,"col":7,"weight":1},{"row":6,"col":10,"weight":1},{"row":6,"col":18,"weight":1},{"row":6,"col":22,"weight":1},{"row":6,"col":26,"weight":1},{"row":6,"col":34,"weight":1},{"row":6,"col":38,"weight":1},{"row":7,"col":8,"weight":1},{"row":7,"col":9,"weight":1},{"row":7,"col":11,"weight":1},{"row":7,"col":12,"weight":2},{"row":7,"col":13,"weight":2},{"row":7,"col":14,"weight":2},{"row":7,"col":15,"weight":2},{"row":7,"col":16,"weight":1},{"row":7,"col":17,"weight":1},{"row":7,"col":18,"weight":1},{"row":7,"col":19,"weight":1},{"row":7,"col":20,"weight":1},{"row":7,"col":21,"weight":1},{"row":7,"col":22,"weight":1},{"row":7,"col":23,"weight":1},{"row":7,"col":24,"weight":1},{"row":7,"col":25,"weight":1},{"row":7,"col":32,"weight":1},{"row":7,"col":33,"weight":1},{"row":7,"col":34,"weight":1},{"row":7,"col":35,"weight":1},{"row":7,"col":36,"weight":1},{"row":7,"col":37,"weight":1},{"row":7,"col":39,"weight":1},{"row":8,"col":10,"weight":1},{"row":8,"col":17,"weight":1},{"row":8,"col":18,"weight":1},{"row":8,"col":19,"weight":1},{"row":8,"col":21,"weight":1},{"row":8,"col":22,"weight":1},{"row":8,"col":23,"weight":1},{"row":8,"col":31,"weight":1},{"row":8,"col":33,"weight":1},{"row":8,"col":34,"weight":1},{"row":8,"col":35,"weight":1},{"row":8,"col":38,"weight":1},{"row":9,"col":10,"weight":1},{"row":9,"col":18,"weight":1},{"row":9,"col":22,"weight":1},{"row":9,"col":30,"weight":1},{"row":9,"col":34,"weight":1},{"row":9,"col":38,"weight":2},{"row":10,"col":11,"weight":1},{"row":10,"col":14,"weight":1},{"row":10,"col":18,"weight":1},{"row":10,"col":22,"weight":1},{"row":10,"col":30,"weight":1},{"row":10,"col":34,"weight":1},{"row":10,"col":38,"weight":1},{"row":11,"col":12,"weight":1},{"row":11,"col":13,"weight":1},{"row":11,"col":15,"weight":1},{"row":11,"col":16,"weight":1},{"row":11,"col":17,"weight":1},{"row":11,"col":18,"weight":1},{"row":11,"col":19,"weight":1},{"row":11,"col":20,"weight":1},{"row":11,"col":21,"weight":1},{"row":11,"col":22,"weight":1},{"row":11,"col":23,"weight":1},{"row":11,"col":24,"weight":1},{"row":11,"col":25,"weight":2},{"row":11,"col":26,"weight":2},{"row":11,"col":27,"weight":2},{"row":11,"col":28,"weight":2},{"row":11,"col":29,"weight":1},{"row":11,"col":31,"weight":1},{"row":11,"col":34,"weight":1},{"row":11,"col":38,"weight":1},{"row":12,"col":14,"weight":1},{"row":12,"col":17,"weight":1},{"row":12,"col":18,"weight":1},{"row":12,"col":19,"weight":1},{"row":12,"col":21,"weight":1},{"row":12,"col":22,"weight":1},{"row":12,"col":23,"weight":1},{"row":12,"col":30,"weight":1},{"row":12,"col":34,"weight":1},{"row":12,"col":38,"weight":1},{"row":13,"col":14,"weight":1},{"row":13,"col":18,"weight":1},{"row":13,"col":22,"weight":1},{"row":13,"col":30,"weight":2},{"row":13,"col":34,"weight":1},{"row":13,"col":38,"weight":1},{"row":14,"col":15,"weight":1},{"row":14,"col":18,"weight":1},{"row":14,"col":22,"weight":1},{"row":14,"col":30,"weight":1},{"row":14,"col":34,"weight":1},{"row":14,"col":38,"weight":2},{"row":15,"col":16,"weight":1},{"row":15,"col":17,"weight":1},{"row":15,"col":18,"weight":1},{"row":15,"col":19,"weight":1},{"row":15,"col":20,"weight":1},{"row":15,"col":21,"weight":1},{"row":15,"col":22,"weight":1},{"row":15,"col":23,"weight":1},{"row":15,"col":24,"weight":1},{"row":15,"col":25,"weight":2},{"row":15,"col":26,"weight":2},{"row":15,"col":27,"weight":2},{"row":15,"col":28,"weight":1},{"row":15,"col":29,"weight":1},{"row":15,"col":30,"weight":1},{"row":15,"col":31,"weight":1},{"row":15,"col":32,"weight":1},{"row":15,"col":33,"weight":1},{"row":15,"col":35,"weight":1},{"row":15,"col":38,"weight":2},{"row":16,"col":18,"weight":1},{"row":16,"col":21,"weight":1},{"row":16,"col":22,"weight":1},{"row":16,"col":23,"weight":1},{"row":16,"col":29,"weight":1},{"row":16,"col":30,"weight":1},{"row":16,"col":31,"weight":1},{"row":16,"col":34,"weight":1},{"row":16,"col":38,"weight":2},{"row":17,"col":18,"weight":1},{"row":17,"col":22,"weight":1},{"row":17,"col":30,"weight":1},{"row":17,"col":34,"weight":2},{"row":17,"col":38,"weight":2},{"row":18,"col":19,"weight":1},{"row":18,"col":22,"weight":1},{"row":18,"col":30,"weight":1},{"row":18,"col":34,"weight":1},{"row":18,"col":38,"weight":1},{"row":19,"col":20,"weight":1},{"row":19,"col":21,"weight":1},{"row":19,"col":22,"weight":1},{"row":19,"col":23,"weight":1},{"row":19,"col":24,"weight":1},{"row":19,"col":25,"weight":2},{"row":19,"col":26,"weight":2},{"row":19,"col":27,"weight":2},{"row":19,"col":28,"weight":1},{"row":19,"col":29,"weight":1},{"row":19,"col":30,"weight":1},{"row":19,"col":31,"weight":1},{"row":19,"col":32,"weight":1},{"row":19,"col":33,"weight":1},{"row":19,"col":34,"weight":1},{"row":19,"col":35,"weight":1},{"row":19,"col":36,"weight":1},{"row":19,"col":37,"weight":1},{"row":20,"col":21,"weight":1},{"row":20,"col":22,"weight":1},{"row":20,"col":23,"weight":1},{"row":20,"col":29,"weight":1},{"row":20,"col":30,"weight":1},{"row":20,"col":31,"weight":1},{"row":20,"col":33,"weight":1},{"row":20,"col":34,"weight":1},{"row":20,"col":35,"weight":1},{"row":21,"col":22,"weight":1},{"row":21,"col":30,"weight":1},{"row":21,"col":34,"weight":1},{"row":22,"col":23,"weight":1},{"row":22,"col":30,"weight":1},{"row":22,"col":34,"weight":1},{"row":23,"col":24,"weight":1},{"row":23,"col":25,"weight":2},{"row":23,"col":26,"weight":2},{"row":23,"col":27,"weight":2},{"row":23,"col":28,"weight":2},{"row":23,"col":29,"weight":1},{"row":23,"col":31,"weight":1},{"row":23,"col":32,"weight":2},{"row":23,"col":33,"weight":1},{"row":24,"col":30,"weight":1}],"edges":[[0,3],[0,4],[1,14],[1,15],[2,23],[2,24],[3,27],[4,5],[4,27],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11],[11,12],[12,13],[13,14],[14,28],[15,16],[15,28],[16,17],[17,29],[18,19],[18,30],[19,20],[20,21],[21,22],[22,23],[23,31],[24,25],[24,31],[25,26],[26,32],[27,33],[28,34],[29,35],[30,36],[31,37],[32,38],[33,39],[34,41],[35,42],[36,43],[37,44],[38,45],[39,46],[40,47],[40,48],[41,54],[41,55],[41,56],[42,58],[42,59],[42,60],[43,62],[44,64],[44,65],[44,66],[45,68],[45,69],[46,47],[47,70],[48,49],[48,70],[49,50],[50,51],[51,52],[52,53],[53,54],[53,71],[54,55],[54,71],[54,72],[55,56],[55,71],[55,72],[55,73],[56,57],[56,72],[56,73],[57,58],[57,73],[57,74],[58,59],[58,74],[58,75],[59,60],[59,74],[59,75],[59,76],[60,61],[60,75],[60,76],[61,62],[61,76],[63,64],[63,77],[63,78],[64,65],[64,78],[64,79],[65,66],[65,78],[65,79],[65,80],[66,67],[66,79],[66,80],[67,68],[67,80],[68,81],[69,81],[70,82],[71,72],[71,83],[72,73],[72,83],[73,83],[74,75],[74,84],[75,76],[75,84],[76,84],[77,85],[78,79],[78,86],[79,80],[79,86],[80,86],[81,87],[82,88],[83,90],[84,91],[85,92],[86,93],[87,94],[88,95],[89,96],[89,97],[90,99],[90,100],[90,101],[91,103],[91,104],[91,105],[92,111],[92,112],[93,113],[94,114],[95,96],[96,115],[97,98],[97,115],[98,99],[98,116],[99,100],[99,116],[99,117],[100,101],[100,116],[100,117],[100,118],[101,102],[101,117],[101,118],[102,103],[102,118],[102,119],[103,104],[103,119],[103,120],[104,105],[104,119],[104,120],[104,121],[105,106],[105,120],[105,121],[106,107],[106,121],[107,108],[108,109],[109,110],[110,111],[111,122],[112,122],[113,123],[114,124],[115,125],[116,117],[116,126],[117,118],[117,126],[118,126],[119,120],[119,127],[120,121],[120,127],[121,127],[122,128],[123,129],[124,130],[125,131],[126,132],[127,133],[128,134],[129,135],[130,136],[131,137],[132,138],[132,139],[132,140],[133,142],[133,143],[133,144],[134,150],[134,151],[134,152],[135,154],[135,155],[136,156],[137,138],[138,139],[138,157],[139,140],[139,157],[140,141],[140,157],[141,142],[141,158],[142,143],[142,158],[142,159],[143,144],[143,158],[143,159],[143,160],[144,145],[144,159],[144,160],[145,146],[145,160],[146,147],[147,148],[148,149],[149,150],[149,161],[150,151],[150,161],[150,162],[151,152],[151,161],[151,162],[151,163],[152,153],[152,162],[152,163],[153,154],[153,163],[154,164],[155,164],[156,165],[157,166],[158,159],[158,167],[159,160],[159,167],[160,167],[161,162],[161,168],[162,163],[162,168],[163,168],[164,169],[165,170],[166,171],[167,172],[168,173],[169,174],[170,175],[171,176],[172,177],[172,178],[172,179],[173,185],[173,186],[173,187],[174,189],[174,190],[174,191],[175,193],[176,177],[176,194],[177,178],[177,194],[177,195],[178,179],[178,194],[178,195],[178,196],[179,180],[179,195],[179,196],[180,181],[180,196],[181,182],[182,183],[183,184],[184,185],[184,197],[185,186],[185,197],[185,198],[186,187],[186,197],[186,198],[186,199],[187,188],[187,198],[187,199],[188,189],[188,199],[188,200],[189,190],[189,200],[189,201],[190,191],[190,200],[190,201],[190,202],[191,192],[191,201],[191,202],[192,193],[192,202],[194,195],[194,203],[195,196],[195,203],[196,203],[197,198],[197,204],[198,199],[198,204],[199,204],[200,201],[200,205],[201,202],[201,205],[202,205],[203,206],[204,207],[205,208],[206,209],[207,214],[207,215],[208,217],[209,210],[210,211],[211,212],[212,213],[213,214],[214,218],[215,216],[215,218],[216,217]],"mis_overhead":89,"padding":2,"spacing":4,"weighted":false} \ No newline at end of file diff --git a/docs/paper/static/petersen_square_weighted.json b/docs/paper/static/petersen_square_weighted.json index 4986741e..e22db6fd 100644 --- a/docs/paper/static/petersen_square_weighted.json +++ b/docs/paper/static/petersen_square_weighted.json @@ -1,2581 +1 @@ -{ - "grid_graph": { - "grid_type": "Square", - "size": [ - 30, - 42 - ], - "nodes": [ - { - "row": 2, - "col": 6, - "weight": 2 - }, - { - "row": 2, - "col": 18, - "weight": 2 - }, - { - "row": 2, - "col": 34, - "weight": 2 - }, - { - "row": 3, - "col": 5, - "weight": 1 - }, - { - "row": 3, - "col": 7, - "weight": 2 - }, - { - "row": 3, - "col": 8, - "weight": 2 - }, - { - "row": 3, - "col": 9, - "weight": 2 - }, - { - "row": 3, - "col": 10, - "weight": 2 - }, - { - "row": 3, - "col": 11, - "weight": 2 - }, - { - "row": 3, - "col": 12, - "weight": 2 - }, - { - "row": 3, - "col": 13, - "weight": 2 - }, - { - "row": 3, - "col": 14, - "weight": 2 - }, - { - "row": 3, - "col": 15, - "weight": 2 - }, - { - "row": 3, - "col": 16, - "weight": 2 - }, - { - "row": 3, - "col": 17, - "weight": 2 - }, - { - "row": 3, - "col": 19, - "weight": 2 - }, - { - "row": 3, - "col": 20, - "weight": 2 - }, - { - "row": 3, - "col": 21, - "weight": 1 - }, - { - "row": 3, - "col": 28, - "weight": 2 - }, - { - "row": 3, - "col": 29, - "weight": 2 - }, - { - "row": 3, - "col": 30, - "weight": 2 - }, - { - "row": 3, - "col": 31, - "weight": 2 - }, - { - "row": 3, - "col": 32, - "weight": 2 - }, - { - "row": 3, - "col": 33, - "weight": 2 - }, - { - "row": 3, - "col": 35, - "weight": 2 - }, - { - "row": 3, - "col": 36, - "weight": 2 - }, - { - "row": 3, - "col": 37, - "weight": 1 - }, - { - "row": 4, - "col": 6, - "weight": 1 - }, - { - "row": 4, - "col": 18, - "weight": 1 - }, - { - "row": 4, - "col": 22, - "weight": 1 - }, - { - "row": 4, - "col": 27, - "weight": 2 - }, - { - "row": 4, - "col": 34, - "weight": 1 - }, - { - "row": 4, - "col": 38, - "weight": 1 - }, - { - "row": 5, - "col": 6, - "weight": 2 - }, - { - "row": 5, - "col": 18, - "weight": 2 - }, - { - "row": 5, - "col": 22, - "weight": 2 - }, - { - "row": 5, - "col": 26, - "weight": 2 - }, - { - "row": 5, - "col": 34, - "weight": 2 - }, - { - "row": 5, - "col": 38, - "weight": 2 - }, - { - "row": 6, - "col": 7, - "weight": 2 - }, - { - "row": 6, - "col": 10, - "weight": 2 - }, - { - "row": 6, - "col": 18, - "weight": 2 - }, - { - "row": 6, - "col": 22, - "weight": 2 - }, - { - "row": 6, - "col": 26, - "weight": 1 - }, - { - "row": 6, - "col": 34, - "weight": 2 - }, - { - "row": 6, - "col": 38, - "weight": 2 - }, - { - "row": 7, - "col": 8, - "weight": 2 - }, - { - "row": 7, - "col": 9, - "weight": 2 - }, - { - "row": 7, - "col": 11, - "weight": 2 - }, - { - "row": 7, - "col": 12, - "weight": 2 - }, - { - "row": 7, - "col": 13, - "weight": 2 - }, - { - "row": 7, - "col": 14, - "weight": 2 - }, - { - "row": 7, - "col": 15, - "weight": 2 - }, - { - "row": 7, - "col": 16, - "weight": 2 - }, - { - "row": 7, - "col": 17, - "weight": 2 - }, - { - "row": 7, - "col": 18, - "weight": 2 - }, - { - "row": 7, - "col": 19, - "weight": 2 - }, - { - "row": 7, - "col": 20, - "weight": 2 - }, - { - "row": 7, - "col": 21, - "weight": 2 - }, - { - "row": 7, - "col": 22, - "weight": 2 - }, - { - "row": 7, - "col": 23, - "weight": 2 - }, - { - "row": 7, - "col": 24, - "weight": 2 - }, - { - "row": 7, - "col": 25, - "weight": 1 - }, - { - "row": 7, - "col": 32, - "weight": 2 - }, - { - "row": 7, - "col": 33, - "weight": 2 - }, - { - "row": 7, - "col": 34, - "weight": 2 - }, - { - "row": 7, - "col": 35, - "weight": 2 - }, - { - "row": 7, - "col": 36, - "weight": 2 - }, - { - "row": 7, - "col": 37, - "weight": 1 - }, - { - "row": 7, - "col": 39, - "weight": 2 - }, - { - "row": 8, - "col": 10, - "weight": 1 - }, - { - "row": 8, - "col": 17, - "weight": 2 - }, - { - "row": 8, - "col": 18, - "weight": 2 - }, - { - "row": 8, - "col": 19, - "weight": 2 - }, - { - "row": 8, - "col": 21, - "weight": 2 - }, - { - "row": 8, - "col": 22, - "weight": 2 - }, - { - "row": 8, - "col": 23, - "weight": 2 - }, - { - "row": 8, - "col": 31, - "weight": 2 - }, - { - "row": 8, - "col": 33, - "weight": 2 - }, - { - "row": 8, - "col": 34, - "weight": 2 - }, - { - "row": 8, - "col": 35, - "weight": 2 - }, - { - "row": 8, - "col": 38, - "weight": 2 - }, - { - "row": 9, - "col": 10, - "weight": 2 - }, - { - "row": 9, - "col": 18, - "weight": 2 - }, - { - "row": 9, - "col": 22, - "weight": 2 - }, - { - "row": 9, - "col": 30, - "weight": 2 - }, - { - "row": 9, - "col": 34, - "weight": 2 - }, - { - "row": 9, - "col": 38, - "weight": 2 - }, - { - "row": 10, - "col": 11, - "weight": 2 - }, - { - "row": 10, - "col": 14, - "weight": 2 - }, - { - "row": 10, - "col": 18, - "weight": 2 - }, - { - "row": 10, - "col": 22, - "weight": 2 - }, - { - "row": 10, - "col": 30, - "weight": 2 - }, - { - "row": 10, - "col": 34, - "weight": 2 - }, - { - "row": 10, - "col": 38, - "weight": 2 - }, - { - "row": 11, - "col": 12, - "weight": 2 - }, - { - "row": 11, - "col": 13, - "weight": 2 - }, - { - "row": 11, - "col": 15, - "weight": 2 - }, - { - "row": 11, - "col": 16, - "weight": 2 - }, - { - "row": 11, - "col": 17, - "weight": 2 - }, - { - "row": 11, - "col": 18, - "weight": 2 - }, - { - "row": 11, - "col": 19, - "weight": 2 - }, - { - "row": 11, - "col": 20, - "weight": 2 - }, - { - "row": 11, - "col": 21, - "weight": 2 - }, - { - "row": 11, - "col": 22, - "weight": 2 - }, - { - "row": 11, - "col": 23, - "weight": 2 - }, - { - "row": 11, - "col": 24, - "weight": 2 - }, - { - "row": 11, - "col": 25, - "weight": 2 - }, - { - "row": 11, - "col": 26, - "weight": 2 - }, - { - "row": 11, - "col": 27, - "weight": 2 - }, - { - "row": 11, - "col": 28, - "weight": 2 - }, - { - "row": 11, - "col": 29, - "weight": 1 - }, - { - "row": 11, - "col": 31, - "weight": 2 - }, - { - "row": 11, - "col": 34, - "weight": 2 - }, - { - "row": 11, - "col": 38, - "weight": 2 - }, - { - "row": 12, - "col": 14, - "weight": 1 - }, - { - "row": 12, - "col": 17, - "weight": 2 - }, - { - "row": 12, - "col": 18, - "weight": 2 - }, - { - "row": 12, - "col": 19, - "weight": 2 - }, - { - "row": 12, - "col": 21, - "weight": 2 - }, - { - "row": 12, - "col": 22, - "weight": 2 - }, - { - "row": 12, - "col": 23, - "weight": 2 - }, - { - "row": 12, - "col": 30, - "weight": 2 - }, - { - "row": 12, - "col": 34, - "weight": 2 - }, - { - "row": 12, - "col": 38, - "weight": 2 - }, - { - "row": 13, - "col": 14, - "weight": 2 - }, - { - "row": 13, - "col": 18, - "weight": 2 - }, - { - "row": 13, - "col": 22, - "weight": 2 - }, - { - "row": 13, - "col": 30, - "weight": 2 - }, - { - "row": 13, - "col": 34, - "weight": 2 - }, - { - "row": 13, - "col": 38, - "weight": 2 - }, - { - "row": 14, - "col": 15, - "weight": 2 - }, - { - "row": 14, - "col": 18, - "weight": 2 - }, - { - "row": 14, - "col": 22, - "weight": 2 - }, - { - "row": 14, - "col": 30, - "weight": 2 - }, - { - "row": 14, - "col": 34, - "weight": 2 - }, - { - "row": 14, - "col": 38, - "weight": 2 - }, - { - "row": 15, - "col": 16, - "weight": 2 - }, - { - "row": 15, - "col": 17, - "weight": 2 - }, - { - "row": 15, - "col": 18, - "weight": 2 - }, - { - "row": 15, - "col": 19, - "weight": 2 - }, - { - "row": 15, - "col": 20, - "weight": 2 - }, - { - "row": 15, - "col": 21, - "weight": 2 - }, - { - "row": 15, - "col": 22, - "weight": 2 - }, - { - "row": 15, - "col": 23, - "weight": 2 - }, - { - "row": 15, - "col": 24, - "weight": 2 - }, - { - "row": 15, - "col": 25, - "weight": 2 - }, - { - "row": 15, - "col": 26, - "weight": 2 - }, - { - "row": 15, - "col": 27, - "weight": 2 - }, - { - "row": 15, - "col": 28, - "weight": 2 - }, - { - "row": 15, - "col": 29, - "weight": 2 - }, - { - "row": 15, - "col": 30, - "weight": 2 - }, - { - "row": 15, - "col": 31, - "weight": 2 - }, - { - "row": 15, - "col": 32, - "weight": 2 - }, - { - "row": 15, - "col": 33, - "weight": 1 - }, - { - "row": 15, - "col": 35, - "weight": 2 - }, - { - "row": 15, - "col": 38, - "weight": 2 - }, - { - "row": 16, - "col": 18, - "weight": 2 - }, - { - "row": 16, - "col": 21, - "weight": 2 - }, - { - "row": 16, - "col": 22, - "weight": 2 - }, - { - "row": 16, - "col": 23, - "weight": 2 - }, - { - "row": 16, - "col": 29, - "weight": 2 - }, - { - "row": 16, - "col": 30, - "weight": 2 - }, - { - "row": 16, - "col": 31, - "weight": 2 - }, - { - "row": 16, - "col": 34, - "weight": 2 - }, - { - "row": 16, - "col": 38, - "weight": 2 - }, - { - "row": 17, - "col": 18, - "weight": 2 - }, - { - "row": 17, - "col": 22, - "weight": 2 - }, - { - "row": 17, - "col": 30, - "weight": 2 - }, - { - "row": 17, - "col": 34, - "weight": 2 - }, - { - "row": 17, - "col": 38, - "weight": 2 - }, - { - "row": 18, - "col": 19, - "weight": 2 - }, - { - "row": 18, - "col": 22, - "weight": 2 - }, - { - "row": 18, - "col": 30, - "weight": 2 - }, - { - "row": 18, - "col": 34, - "weight": 2 - }, - { - "row": 18, - "col": 38, - "weight": 1 - }, - { - "row": 19, - "col": 20, - "weight": 2 - }, - { - "row": 19, - "col": 21, - "weight": 2 - }, - { - "row": 19, - "col": 22, - "weight": 2 - }, - { - "row": 19, - "col": 23, - "weight": 2 - }, - { - "row": 19, - "col": 24, - "weight": 2 - }, - { - "row": 19, - "col": 25, - "weight": 2 - }, - { - "row": 19, - "col": 26, - "weight": 2 - }, - { - "row": 19, - "col": 27, - "weight": 2 - }, - { - "row": 19, - "col": 28, - "weight": 2 - }, - { - "row": 19, - "col": 29, - "weight": 2 - }, - { - "row": 19, - "col": 30, - "weight": 2 - }, - { - "row": 19, - "col": 31, - "weight": 2 - }, - { - "row": 19, - "col": 32, - "weight": 2 - }, - { - "row": 19, - "col": 33, - "weight": 2 - }, - { - "row": 19, - "col": 34, - "weight": 2 - }, - { - "row": 19, - "col": 35, - "weight": 2 - }, - { - "row": 19, - "col": 36, - "weight": 2 - }, - { - "row": 19, - "col": 37, - "weight": 1 - }, - { - "row": 20, - "col": 21, - "weight": 2 - }, - { - "row": 20, - "col": 22, - "weight": 2 - }, - { - "row": 20, - "col": 23, - "weight": 2 - }, - { - "row": 20, - "col": 29, - "weight": 2 - }, - { - "row": 20, - "col": 30, - "weight": 2 - }, - { - "row": 20, - "col": 31, - "weight": 2 - }, - { - "row": 20, - "col": 33, - "weight": 2 - }, - { - "row": 20, - "col": 34, - "weight": 2 - }, - { - "row": 20, - "col": 35, - "weight": 2 - }, - { - "row": 21, - "col": 22, - "weight": 2 - }, - { - "row": 21, - "col": 30, - "weight": 2 - }, - { - "row": 21, - "col": 34, - "weight": 2 - }, - { - "row": 22, - "col": 23, - "weight": 2 - }, - { - "row": 22, - "col": 30, - "weight": 1 - }, - { - "row": 22, - "col": 34, - "weight": 1 - }, - { - "row": 23, - "col": 24, - "weight": 2 - }, - { - "row": 23, - "col": 25, - "weight": 2 - }, - { - "row": 23, - "col": 26, - "weight": 2 - }, - { - "row": 23, - "col": 27, - "weight": 2 - }, - { - "row": 23, - "col": 28, - "weight": 2 - }, - { - "row": 23, - "col": 29, - "weight": 2 - }, - { - "row": 23, - "col": 31, - "weight": 2 - }, - { - "row": 23, - "col": 32, - "weight": 2 - }, - { - "row": 23, - "col": 33, - "weight": 1 - }, - { - "row": 24, - "col": 30, - "weight": 2 - } - ], - "radius": 1.5, - "edges": [ - [ - 0, - 3 - ], - [ - 0, - 4 - ], - [ - 1, - 14 - ], - [ - 1, - 15 - ], - [ - 2, - 23 - ], - [ - 2, - 24 - ], - [ - 3, - 27 - ], - [ - 4, - 5 - ], - [ - 4, - 27 - ], - [ - 5, - 6 - ], - [ - 6, - 7 - ], - [ - 7, - 8 - ], - [ - 8, - 9 - ], - [ - 9, - 10 - ], - [ - 10, - 11 - ], - [ - 11, - 12 - ], - [ - 12, - 13 - ], - [ - 13, - 14 - ], - [ - 14, - 28 - ], - [ - 15, - 16 - ], - [ - 15, - 28 - ], - [ - 16, - 17 - ], - [ - 17, - 29 - ], - [ - 18, - 19 - ], - [ - 18, - 30 - ], - [ - 19, - 20 - ], - [ - 20, - 21 - ], - [ - 21, - 22 - ], - [ - 22, - 23 - ], - [ - 23, - 31 - ], - [ - 24, - 25 - ], - [ - 24, - 31 - ], - [ - 25, - 26 - ], - [ - 26, - 32 - ], - [ - 27, - 33 - ], - [ - 28, - 34 - ], - [ - 29, - 35 - ], - [ - 30, - 36 - ], - [ - 31, - 37 - ], - [ - 32, - 38 - ], - [ - 33, - 39 - ], - [ - 34, - 41 - ], - [ - 35, - 42 - ], - [ - 36, - 43 - ], - [ - 37, - 44 - ], - [ - 38, - 45 - ], - [ - 39, - 46 - ], - [ - 40, - 47 - ], - [ - 40, - 48 - ], - [ - 41, - 54 - ], - [ - 41, - 55 - ], - [ - 41, - 56 - ], - [ - 42, - 58 - ], - [ - 42, - 59 - ], - [ - 42, - 60 - ], - [ - 43, - 62 - ], - [ - 44, - 64 - ], - [ - 44, - 65 - ], - [ - 44, - 66 - ], - [ - 45, - 68 - ], - [ - 45, - 69 - ], - [ - 46, - 47 - ], - [ - 47, - 70 - ], - [ - 48, - 49 - ], - [ - 48, - 70 - ], - [ - 49, - 50 - ], - [ - 50, - 51 - ], - [ - 51, - 52 - ], - [ - 52, - 53 - ], - [ - 53, - 54 - ], - [ - 53, - 71 - ], - [ - 54, - 55 - ], - [ - 54, - 71 - ], - [ - 54, - 72 - ], - [ - 55, - 56 - ], - [ - 55, - 71 - ], - [ - 55, - 72 - ], - [ - 55, - 73 - ], - [ - 56, - 57 - ], - [ - 56, - 72 - ], - [ - 56, - 73 - ], - [ - 57, - 58 - ], - [ - 57, - 73 - ], - [ - 57, - 74 - ], - [ - 58, - 59 - ], - [ - 58, - 74 - ], - [ - 58, - 75 - ], - [ - 59, - 60 - ], - [ - 59, - 74 - ], - [ - 59, - 75 - ], - [ - 59, - 76 - ], - [ - 60, - 61 - ], - [ - 60, - 75 - ], - [ - 60, - 76 - ], - [ - 61, - 62 - ], - [ - 61, - 76 - ], - [ - 63, - 64 - ], - [ - 63, - 77 - ], - [ - 63, - 78 - ], - [ - 64, - 65 - ], - [ - 64, - 78 - ], - [ - 64, - 79 - ], - [ - 65, - 66 - ], - [ - 65, - 78 - ], - [ - 65, - 79 - ], - [ - 65, - 80 - ], - [ - 66, - 67 - ], - [ - 66, - 79 - ], - [ - 66, - 80 - ], - [ - 67, - 68 - ], - [ - 67, - 80 - ], - [ - 68, - 81 - ], - [ - 69, - 81 - ], - [ - 70, - 82 - ], - [ - 71, - 72 - ], - [ - 71, - 83 - ], - [ - 72, - 73 - ], - [ - 72, - 83 - ], - [ - 73, - 83 - ], - [ - 74, - 75 - ], - [ - 74, - 84 - ], - [ - 75, - 76 - ], - [ - 75, - 84 - ], - [ - 76, - 84 - ], - [ - 77, - 85 - ], - [ - 78, - 79 - ], - [ - 78, - 86 - ], - [ - 79, - 80 - ], - [ - 79, - 86 - ], - [ - 80, - 86 - ], - [ - 81, - 87 - ], - [ - 82, - 88 - ], - [ - 83, - 90 - ], - [ - 84, - 91 - ], - [ - 85, - 92 - ], - [ - 86, - 93 - ], - [ - 87, - 94 - ], - [ - 88, - 95 - ], - [ - 89, - 96 - ], - [ - 89, - 97 - ], - [ - 90, - 99 - ], - [ - 90, - 100 - ], - [ - 90, - 101 - ], - [ - 91, - 103 - ], - [ - 91, - 104 - ], - [ - 91, - 105 - ], - [ - 92, - 111 - ], - [ - 92, - 112 - ], - [ - 93, - 113 - ], - [ - 94, - 114 - ], - [ - 95, - 96 - ], - [ - 96, - 115 - ], - [ - 97, - 98 - ], - [ - 97, - 115 - ], - [ - 98, - 99 - ], - [ - 98, - 116 - ], - [ - 99, - 100 - ], - [ - 99, - 116 - ], - [ - 99, - 117 - ], - [ - 100, - 101 - ], - [ - 100, - 116 - ], - [ - 100, - 117 - ], - [ - 100, - 118 - ], - [ - 101, - 102 - ], - [ - 101, - 117 - ], - [ - 101, - 118 - ], - [ - 102, - 103 - ], - [ - 102, - 118 - ], - [ - 102, - 119 - ], - [ - 103, - 104 - ], - [ - 103, - 119 - ], - [ - 103, - 120 - ], - [ - 104, - 105 - ], - [ - 104, - 119 - ], - [ - 104, - 120 - ], - [ - 104, - 121 - ], - [ - 105, - 106 - ], - [ - 105, - 120 - ], - [ - 105, - 121 - ], - [ - 106, - 107 - ], - [ - 106, - 121 - ], - [ - 107, - 108 - ], - [ - 108, - 109 - ], - [ - 109, - 110 - ], - [ - 110, - 111 - ], - [ - 111, - 122 - ], - [ - 112, - 122 - ], - [ - 113, - 123 - ], - [ - 114, - 124 - ], - [ - 115, - 125 - ], - [ - 116, - 117 - ], - [ - 116, - 126 - ], - [ - 117, - 118 - ], - [ - 117, - 126 - ], - [ - 118, - 126 - ], - [ - 119, - 120 - ], - [ - 119, - 127 - ], - [ - 120, - 121 - ], - [ - 120, - 127 - ], - [ - 121, - 127 - ], - [ - 122, - 128 - ], - [ - 123, - 129 - ], - [ - 124, - 130 - ], - [ - 125, - 131 - ], - [ - 126, - 132 - ], - [ - 127, - 133 - ], - [ - 128, - 134 - ], - [ - 129, - 135 - ], - [ - 130, - 136 - ], - [ - 131, - 137 - ], - [ - 132, - 138 - ], - [ - 132, - 139 - ], - [ - 132, - 140 - ], - [ - 133, - 142 - ], - [ - 133, - 143 - ], - [ - 133, - 144 - ], - [ - 134, - 150 - ], - [ - 134, - 151 - ], - [ - 134, - 152 - ], - [ - 135, - 154 - ], - [ - 135, - 155 - ], - [ - 136, - 156 - ], - [ - 137, - 138 - ], - [ - 138, - 139 - ], - [ - 138, - 157 - ], - [ - 139, - 140 - ], - [ - 139, - 157 - ], - [ - 140, - 141 - ], - [ - 140, - 157 - ], - [ - 141, - 142 - ], - [ - 141, - 158 - ], - [ - 142, - 143 - ], - [ - 142, - 158 - ], - [ - 142, - 159 - ], - [ - 143, - 144 - ], - [ - 143, - 158 - ], - [ - 143, - 159 - ], - [ - 143, - 160 - ], - [ - 144, - 145 - ], - [ - 144, - 159 - ], - [ - 144, - 160 - ], - [ - 145, - 146 - ], - [ - 145, - 160 - ], - [ - 146, - 147 - ], - [ - 147, - 148 - ], - [ - 148, - 149 - ], - [ - 149, - 150 - ], - [ - 149, - 161 - ], - [ - 150, - 151 - ], - [ - 150, - 161 - ], - [ - 150, - 162 - ], - [ - 151, - 152 - ], - [ - 151, - 161 - ], - [ - 151, - 162 - ], - [ - 151, - 163 - ], - [ - 152, - 153 - ], - [ - 152, - 162 - ], - [ - 152, - 163 - ], - [ - 153, - 154 - ], - [ - 153, - 163 - ], - [ - 154, - 164 - ], - [ - 155, - 164 - ], - [ - 156, - 165 - ], - [ - 157, - 166 - ], - [ - 158, - 159 - ], - [ - 158, - 167 - ], - [ - 159, - 160 - ], - [ - 159, - 167 - ], - [ - 160, - 167 - ], - [ - 161, - 162 - ], - [ - 161, - 168 - ], - [ - 162, - 163 - ], - [ - 162, - 168 - ], - [ - 163, - 168 - ], - [ - 164, - 169 - ], - [ - 165, - 170 - ], - [ - 166, - 171 - ], - [ - 167, - 172 - ], - [ - 168, - 173 - ], - [ - 169, - 174 - ], - [ - 170, - 175 - ], - [ - 171, - 176 - ], - [ - 172, - 177 - ], - [ - 172, - 178 - ], - [ - 172, - 179 - ], - [ - 173, - 185 - ], - [ - 173, - 186 - ], - [ - 173, - 187 - ], - [ - 174, - 189 - ], - [ - 174, - 190 - ], - [ - 174, - 191 - ], - [ - 175, - 193 - ], - [ - 176, - 177 - ], - [ - 176, - 194 - ], - [ - 177, - 178 - ], - [ - 177, - 194 - ], - [ - 177, - 195 - ], - [ - 178, - 179 - ], - [ - 178, - 194 - ], - [ - 178, - 195 - ], - [ - 178, - 196 - ], - [ - 179, - 180 - ], - [ - 179, - 195 - ], - [ - 179, - 196 - ], - [ - 180, - 181 - ], - [ - 180, - 196 - ], - [ - 181, - 182 - ], - [ - 182, - 183 - ], - [ - 183, - 184 - ], - [ - 184, - 185 - ], - [ - 184, - 197 - ], - [ - 185, - 186 - ], - [ - 185, - 197 - ], - [ - 185, - 198 - ], - [ - 186, - 187 - ], - [ - 186, - 197 - ], - [ - 186, - 198 - ], - [ - 186, - 199 - ], - [ - 187, - 188 - ], - [ - 187, - 198 - ], - [ - 187, - 199 - ], - [ - 188, - 189 - ], - [ - 188, - 199 - ], - [ - 188, - 200 - ], - [ - 189, - 190 - ], - [ - 189, - 200 - ], - [ - 189, - 201 - ], - [ - 190, - 191 - ], - [ - 190, - 200 - ], - [ - 190, - 201 - ], - [ - 190, - 202 - ], - [ - 191, - 192 - ], - [ - 191, - 201 - ], - [ - 191, - 202 - ], - [ - 192, - 193 - ], - [ - 192, - 202 - ], - [ - 194, - 195 - ], - [ - 194, - 203 - ], - [ - 195, - 196 - ], - [ - 195, - 203 - ], - [ - 196, - 203 - ], - [ - 197, - 198 - ], - [ - 197, - 204 - ], - [ - 198, - 199 - ], - [ - 198, - 204 - ], - [ - 199, - 204 - ], - [ - 200, - 201 - ], - [ - 200, - 205 - ], - [ - 201, - 202 - ], - [ - 201, - 205 - ], - [ - 202, - 205 - ], - [ - 203, - 206 - ], - [ - 204, - 207 - ], - [ - 205, - 208 - ], - [ - 206, - 209 - ], - [ - 207, - 214 - ], - [ - 207, - 215 - ], - [ - 208, - 217 - ], - [ - 209, - 210 - ], - [ - 210, - 211 - ], - [ - 211, - 212 - ], - [ - 212, - 213 - ], - [ - 213, - 214 - ], - [ - 214, - 218 - ], - [ - 215, - 216 - ], - [ - 215, - 218 - ], - [ - 216, - 217 - ] - ] - }, - "mis_overhead": 178, - "padding": 2, - "spacing": 4, - "weighted": true -} \ No newline at end of file +{"nodes":[{"row":2,"col":6,"weight":2},{"row":2,"col":18,"weight":2},{"row":2,"col":34,"weight":2},{"row":3,"col":5,"weight":1},{"row":3,"col":7,"weight":2},{"row":3,"col":8,"weight":2},{"row":3,"col":9,"weight":2},{"row":3,"col":10,"weight":2},{"row":3,"col":11,"weight":2},{"row":3,"col":12,"weight":2},{"row":3,"col":13,"weight":2},{"row":3,"col":14,"weight":2},{"row":3,"col":15,"weight":2},{"row":3,"col":16,"weight":2},{"row":3,"col":17,"weight":2},{"row":3,"col":19,"weight":2},{"row":3,"col":20,"weight":2},{"row":3,"col":21,"weight":1},{"row":3,"col":28,"weight":2},{"row":3,"col":29,"weight":2},{"row":3,"col":30,"weight":2},{"row":3,"col":31,"weight":2},{"row":3,"col":32,"weight":2},{"row":3,"col":33,"weight":2},{"row":3,"col":35,"weight":2},{"row":3,"col":36,"weight":2},{"row":3,"col":37,"weight":1},{"row":4,"col":6,"weight":1},{"row":4,"col":18,"weight":1},{"row":4,"col":22,"weight":1},{"row":4,"col":27,"weight":2},{"row":4,"col":34,"weight":1},{"row":4,"col":38,"weight":1},{"row":5,"col":6,"weight":2},{"row":5,"col":18,"weight":2},{"row":5,"col":22,"weight":2},{"row":5,"col":26,"weight":2},{"row":5,"col":34,"weight":2},{"row":5,"col":38,"weight":2},{"row":6,"col":7,"weight":2},{"row":6,"col":10,"weight":2},{"row":6,"col":18,"weight":2},{"row":6,"col":22,"weight":2},{"row":6,"col":26,"weight":1},{"row":6,"col":34,"weight":2},{"row":6,"col":38,"weight":2},{"row":7,"col":8,"weight":2},{"row":7,"col":9,"weight":2},{"row":7,"col":11,"weight":2},{"row":7,"col":12,"weight":2},{"row":7,"col":13,"weight":2},{"row":7,"col":14,"weight":2},{"row":7,"col":15,"weight":2},{"row":7,"col":16,"weight":2},{"row":7,"col":17,"weight":2},{"row":7,"col":18,"weight":2},{"row":7,"col":19,"weight":2},{"row":7,"col":20,"weight":2},{"row":7,"col":21,"weight":2},{"row":7,"col":22,"weight":2},{"row":7,"col":23,"weight":2},{"row":7,"col":24,"weight":2},{"row":7,"col":25,"weight":1},{"row":7,"col":32,"weight":2},{"row":7,"col":33,"weight":2},{"row":7,"col":34,"weight":2},{"row":7,"col":35,"weight":2},{"row":7,"col":36,"weight":2},{"row":7,"col":37,"weight":1},{"row":7,"col":39,"weight":2},{"row":8,"col":10,"weight":1},{"row":8,"col":17,"weight":2},{"row":8,"col":18,"weight":2},{"row":8,"col":19,"weight":2},{"row":8,"col":21,"weight":2},{"row":8,"col":22,"weight":2},{"row":8,"col":23,"weight":2},{"row":8,"col":31,"weight":2},{"row":8,"col":33,"weight":2},{"row":8,"col":34,"weight":2},{"row":8,"col":35,"weight":2},{"row":8,"col":38,"weight":2},{"row":9,"col":10,"weight":2},{"row":9,"col":18,"weight":2},{"row":9,"col":22,"weight":2},{"row":9,"col":30,"weight":2},{"row":9,"col":34,"weight":2},{"row":9,"col":38,"weight":2},{"row":10,"col":11,"weight":2},{"row":10,"col":14,"weight":2},{"row":10,"col":18,"weight":2},{"row":10,"col":22,"weight":2},{"row":10,"col":30,"weight":2},{"row":10,"col":34,"weight":2},{"row":10,"col":38,"weight":2},{"row":11,"col":12,"weight":2},{"row":11,"col":13,"weight":2},{"row":11,"col":15,"weight":2},{"row":11,"col":16,"weight":2},{"row":11,"col":17,"weight":2},{"row":11,"col":18,"weight":2},{"row":11,"col":19,"weight":2},{"row":11,"col":20,"weight":2},{"row":11,"col":21,"weight":2},{"row":11,"col":22,"weight":2},{"row":11,"col":23,"weight":2},{"row":11,"col":24,"weight":2},{"row":11,"col":25,"weight":2},{"row":11,"col":26,"weight":2},{"row":11,"col":27,"weight":2},{"row":11,"col":28,"weight":2},{"row":11,"col":29,"weight":1},{"row":11,"col":31,"weight":2},{"row":11,"col":34,"weight":2},{"row":11,"col":38,"weight":2},{"row":12,"col":14,"weight":1},{"row":12,"col":17,"weight":2},{"row":12,"col":18,"weight":2},{"row":12,"col":19,"weight":2},{"row":12,"col":21,"weight":2},{"row":12,"col":22,"weight":2},{"row":12,"col":23,"weight":2},{"row":12,"col":30,"weight":2},{"row":12,"col":34,"weight":2},{"row":12,"col":38,"weight":2},{"row":13,"col":14,"weight":2},{"row":13,"col":18,"weight":2},{"row":13,"col":22,"weight":2},{"row":13,"col":30,"weight":2},{"row":13,"col":34,"weight":2},{"row":13,"col":38,"weight":2},{"row":14,"col":15,"weight":2},{"row":14,"col":18,"weight":2},{"row":14,"col":22,"weight":2},{"row":14,"col":30,"weight":2},{"row":14,"col":34,"weight":2},{"row":14,"col":38,"weight":2},{"row":15,"col":16,"weight":2},{"row":15,"col":17,"weight":2},{"row":15,"col":18,"weight":2},{"row":15,"col":19,"weight":2},{"row":15,"col":20,"weight":2},{"row":15,"col":21,"weight":2},{"row":15,"col":22,"weight":2},{"row":15,"col":23,"weight":2},{"row":15,"col":24,"weight":2},{"row":15,"col":25,"weight":2},{"row":15,"col":26,"weight":2},{"row":15,"col":27,"weight":2},{"row":15,"col":28,"weight":2},{"row":15,"col":29,"weight":2},{"row":15,"col":30,"weight":2},{"row":15,"col":31,"weight":2},{"row":15,"col":32,"weight":2},{"row":15,"col":33,"weight":1},{"row":15,"col":35,"weight":2},{"row":15,"col":38,"weight":2},{"row":16,"col":18,"weight":2},{"row":16,"col":21,"weight":2},{"row":16,"col":22,"weight":2},{"row":16,"col":23,"weight":2},{"row":16,"col":29,"weight":2},{"row":16,"col":30,"weight":2},{"row":16,"col":31,"weight":2},{"row":16,"col":34,"weight":2},{"row":16,"col":38,"weight":2},{"row":17,"col":18,"weight":2},{"row":17,"col":22,"weight":2},{"row":17,"col":30,"weight":2},{"row":17,"col":34,"weight":2},{"row":17,"col":38,"weight":2},{"row":18,"col":19,"weight":2},{"row":18,"col":22,"weight":2},{"row":18,"col":30,"weight":2},{"row":18,"col":34,"weight":2},{"row":18,"col":38,"weight":1},{"row":19,"col":20,"weight":2},{"row":19,"col":21,"weight":2},{"row":19,"col":22,"weight":2},{"row":19,"col":23,"weight":2},{"row":19,"col":24,"weight":2},{"row":19,"col":25,"weight":2},{"row":19,"col":26,"weight":2},{"row":19,"col":27,"weight":2},{"row":19,"col":28,"weight":2},{"row":19,"col":29,"weight":2},{"row":19,"col":30,"weight":2},{"row":19,"col":31,"weight":2},{"row":19,"col":32,"weight":2},{"row":19,"col":33,"weight":2},{"row":19,"col":34,"weight":2},{"row":19,"col":35,"weight":2},{"row":19,"col":36,"weight":2},{"row":19,"col":37,"weight":1},{"row":20,"col":21,"weight":2},{"row":20,"col":22,"weight":2},{"row":20,"col":23,"weight":2},{"row":20,"col":29,"weight":2},{"row":20,"col":30,"weight":2},{"row":20,"col":31,"weight":2},{"row":20,"col":33,"weight":2},{"row":20,"col":34,"weight":2},{"row":20,"col":35,"weight":2},{"row":21,"col":22,"weight":2},{"row":21,"col":30,"weight":2},{"row":21,"col":34,"weight":2},{"row":22,"col":23,"weight":2},{"row":22,"col":30,"weight":1},{"row":22,"col":34,"weight":1},{"row":23,"col":24,"weight":2},{"row":23,"col":25,"weight":2},{"row":23,"col":26,"weight":2},{"row":23,"col":27,"weight":2},{"row":23,"col":28,"weight":2},{"row":23,"col":29,"weight":2},{"row":23,"col":31,"weight":2},{"row":23,"col":32,"weight":2},{"row":23,"col":33,"weight":1},{"row":24,"col":30,"weight":2}],"edges":[[0,3],[0,4],[1,14],[1,15],[2,23],[2,24],[3,27],[4,5],[4,27],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11],[11,12],[12,13],[13,14],[14,28],[15,16],[15,28],[16,17],[17,29],[18,19],[18,30],[19,20],[20,21],[21,22],[22,23],[23,31],[24,25],[24,31],[25,26],[26,32],[27,33],[28,34],[29,35],[30,36],[31,37],[32,38],[33,39],[34,41],[35,42],[36,43],[37,44],[38,45],[39,46],[40,47],[40,48],[41,54],[41,55],[41,56],[42,58],[42,59],[42,60],[43,62],[44,64],[44,65],[44,66],[45,68],[45,69],[46,47],[47,70],[48,49],[48,70],[49,50],[50,51],[51,52],[52,53],[53,54],[53,71],[54,55],[54,71],[54,72],[55,56],[55,71],[55,72],[55,73],[56,57],[56,72],[56,73],[57,58],[57,73],[57,74],[58,59],[58,74],[58,75],[59,60],[59,74],[59,75],[59,76],[60,61],[60,75],[60,76],[61,62],[61,76],[63,64],[63,77],[63,78],[64,65],[64,78],[64,79],[65,66],[65,78],[65,79],[65,80],[66,67],[66,79],[66,80],[67,68],[67,80],[68,81],[69,81],[70,82],[71,72],[71,83],[72,73],[72,83],[73,83],[74,75],[74,84],[75,76],[75,84],[76,84],[77,85],[78,79],[78,86],[79,80],[79,86],[80,86],[81,87],[82,88],[83,90],[84,91],[85,92],[86,93],[87,94],[88,95],[89,96],[89,97],[90,99],[90,100],[90,101],[91,103],[91,104],[91,105],[92,111],[92,112],[93,113],[94,114],[95,96],[96,115],[97,98],[97,115],[98,99],[98,116],[99,100],[99,116],[99,117],[100,101],[100,116],[100,117],[100,118],[101,102],[101,117],[101,118],[102,103],[102,118],[102,119],[103,104],[103,119],[103,120],[104,105],[104,119],[104,120],[104,121],[105,106],[105,120],[105,121],[106,107],[106,121],[107,108],[108,109],[109,110],[110,111],[111,122],[112,122],[113,123],[114,124],[115,125],[116,117],[116,126],[117,118],[117,126],[118,126],[119,120],[119,127],[120,121],[120,127],[121,127],[122,128],[123,129],[124,130],[125,131],[126,132],[127,133],[128,134],[129,135],[130,136],[131,137],[132,138],[132,139],[132,140],[133,142],[133,143],[133,144],[134,150],[134,151],[134,152],[135,154],[135,155],[136,156],[137,138],[138,139],[138,157],[139,140],[139,157],[140,141],[140,157],[141,142],[141,158],[142,143],[142,158],[142,159],[143,144],[143,158],[143,159],[143,160],[144,145],[144,159],[144,160],[145,146],[145,160],[146,147],[147,148],[148,149],[149,150],[149,161],[150,151],[150,161],[150,162],[151,152],[151,161],[151,162],[151,163],[152,153],[152,162],[152,163],[153,154],[153,163],[154,164],[155,164],[156,165],[157,166],[158,159],[158,167],[159,160],[159,167],[160,167],[161,162],[161,168],[162,163],[162,168],[163,168],[164,169],[165,170],[166,171],[167,172],[168,173],[169,174],[170,175],[171,176],[172,177],[172,178],[172,179],[173,185],[173,186],[173,187],[174,189],[174,190],[174,191],[175,193],[176,177],[176,194],[177,178],[177,194],[177,195],[178,179],[178,194],[178,195],[178,196],[179,180],[179,195],[179,196],[180,181],[180,196],[181,182],[182,183],[183,184],[184,185],[184,197],[185,186],[185,197],[185,198],[186,187],[186,197],[186,198],[186,199],[187,188],[187,198],[187,199],[188,189],[188,199],[188,200],[189,190],[189,200],[189,201],[190,191],[190,200],[190,201],[190,202],[191,192],[191,201],[191,202],[192,193],[192,202],[194,195],[194,203],[195,196],[195,203],[196,203],[197,198],[197,204],[198,199],[198,204],[199,204],[200,201],[200,205],[201,202],[201,205],[202,205],[203,206],[204,207],[205,208],[206,209],[207,214],[207,215],[208,217],[209,210],[210,211],[211,212],[212,213],[213,214],[214,218],[215,216],[215,218],[216,217]],"mis_overhead":178,"padding":2,"spacing":4,"weighted":true} \ No newline at end of file diff --git a/docs/paper/static/petersen_triangular.json b/docs/paper/static/petersen_triangular.json index f0f7a08d..62b2e0b9 100644 --- a/docs/paper/static/petersen_triangular.json +++ b/docs/paper/static/petersen_triangular.json @@ -1,4225 +1 @@ -{ - "grid_graph": { - "grid_type": { - "Triangular": { - "offset_even_cols": true - } - }, - "size": [ - 42, - 60 - ], - "nodes": [ - { - "row": 2, - "col": 40, - "weight": 2 - }, - { - "row": 3, - "col": 5, - "weight": 1 - }, - { - "row": 3, - "col": 6, - "weight": 2 - }, - { - "row": 3, - "col": 8, - "weight": 2 - }, - { - "row": 3, - "col": 10, - "weight": 2 - }, - { - "row": 3, - "col": 11, - "weight": 2 - }, - { - "row": 3, - "col": 12, - "weight": 2 - }, - { - "row": 3, - "col": 13, - "weight": 2 - }, - { - "row": 3, - "col": 14, - "weight": 2 - }, - { - "row": 3, - "col": 15, - "weight": 2 - }, - { - "row": 3, - "col": 16, - "weight": 2 - }, - { - "row": 3, - "col": 17, - "weight": 2 - }, - { - "row": 3, - "col": 18, - "weight": 2 - }, - { - "row": 3, - "col": 19, - "weight": 2 - }, - { - "row": 3, - "col": 20, - "weight": 2 - }, - { - "row": 3, - "col": 21, - "weight": 2 - }, - { - "row": 3, - "col": 22, - "weight": 2 - }, - { - "row": 3, - "col": 23, - "weight": 2 - }, - { - "row": 3, - "col": 24, - "weight": 2 - }, - { - "row": 3, - "col": 26, - "weight": 2 - }, - { - "row": 3, - "col": 28, - "weight": 2 - }, - { - "row": 3, - "col": 29, - "weight": 2 - }, - { - "row": 3, - "col": 30, - "weight": 2 - }, - { - "row": 3, - "col": 39, - "weight": 2 - }, - { - "row": 3, - "col": 41, - "weight": 2 - }, - { - "row": 3, - "col": 42, - "weight": 2 - }, - { - "row": 3, - "col": 43, - "weight": 2 - }, - { - "row": 3, - "col": 44, - "weight": 2 - }, - { - "row": 3, - "col": 45, - "weight": 2 - }, - { - "row": 3, - "col": 46, - "weight": 2 - }, - { - "row": 3, - "col": 47, - "weight": 2 - }, - { - "row": 3, - "col": 48, - "weight": 2 - }, - { - "row": 3, - "col": 50, - "weight": 2 - }, - { - "row": 3, - "col": 52, - "weight": 2 - }, - { - "row": 3, - "col": 53, - "weight": 2 - }, - { - "row": 3, - "col": 54, - "weight": 2 - }, - { - "row": 4, - "col": 7, - "weight": 2 - }, - { - "row": 4, - "col": 8, - "weight": 3 - }, - { - "row": 4, - "col": 9, - "weight": 2 - }, - { - "row": 4, - "col": 25, - "weight": 2 - }, - { - "row": 4, - "col": 26, - "weight": 3 - }, - { - "row": 4, - "col": 27, - "weight": 2 - }, - { - "row": 4, - "col": 31, - "weight": 1 - }, - { - "row": 4, - "col": 32, - "weight": 1 - }, - { - "row": 4, - "col": 38, - "weight": 2 - }, - { - "row": 4, - "col": 39, - "weight": 2 - }, - { - "row": 4, - "col": 49, - "weight": 2 - }, - { - "row": 4, - "col": 50, - "weight": 3 - }, - { - "row": 4, - "col": 51, - "weight": 2 - }, - { - "row": 4, - "col": 55, - "weight": 1 - }, - { - "row": 4, - "col": 56, - "weight": 1 - }, - { - "row": 5, - "col": 8, - "weight": 2 - }, - { - "row": 5, - "col": 26, - "weight": 2 - }, - { - "row": 5, - "col": 32, - "weight": 2 - }, - { - "row": 5, - "col": 38, - "weight": 2 - }, - { - "row": 5, - "col": 50, - "weight": 2 - }, - { - "row": 5, - "col": 56, - "weight": 2 - }, - { - "row": 6, - "col": 8, - "weight": 2 - }, - { - "row": 6, - "col": 26, - "weight": 2 - }, - { - "row": 6, - "col": 32, - "weight": 2 - }, - { - "row": 6, - "col": 38, - "weight": 2 - }, - { - "row": 6, - "col": 50, - "weight": 2 - }, - { - "row": 6, - "col": 56, - "weight": 2 - }, - { - "row": 7, - "col": 8, - "weight": 2 - }, - { - "row": 7, - "col": 26, - "weight": 2 - }, - { - "row": 7, - "col": 32, - "weight": 2 - }, - { - "row": 7, - "col": 38, - "weight": 2 - }, - { - "row": 7, - "col": 50, - "weight": 2 - }, - { - "row": 7, - "col": 56, - "weight": 2 - }, - { - "row": 8, - "col": 8, - "weight": 2 - }, - { - "row": 8, - "col": 26, - "weight": 3 - }, - { - "row": 8, - "col": 32, - "weight": 3 - }, - { - "row": 8, - "col": 38, - "weight": 1 - }, - { - "row": 8, - "col": 46, - "weight": 2 - }, - { - "row": 8, - "col": 50, - "weight": 3 - }, - { - "row": 8, - "col": 56, - "weight": 3 - }, - { - "row": 9, - "col": 8, - "weight": 2 - }, - { - "row": 9, - "col": 10, - "weight": 2 - }, - { - "row": 9, - "col": 11, - "weight": 2 - }, - { - "row": 9, - "col": 12, - "weight": 2 - }, - { - "row": 9, - "col": 14, - "weight": 2 - }, - { - "row": 9, - "col": 16, - "weight": 2 - }, - { - "row": 9, - "col": 17, - "weight": 2 - }, - { - "row": 9, - "col": 18, - "weight": 2 - }, - { - "row": 9, - "col": 19, - "weight": 2 - }, - { - "row": 9, - "col": 20, - "weight": 2 - }, - { - "row": 9, - "col": 21, - "weight": 2 - }, - { - "row": 9, - "col": 22, - "weight": 2 - }, - { - "row": 9, - "col": 23, - "weight": 2 - }, - { - "row": 9, - "col": 24, - "weight": 3 - }, - { - "row": 9, - "col": 25, - "weight": 2 - }, - { - "row": 9, - "col": 26, - "weight": 4 - }, - { - "row": 9, - "col": 27, - "weight": 2 - }, - { - "row": 9, - "col": 28, - "weight": 2 - }, - { - "row": 9, - "col": 29, - "weight": 2 - }, - { - "row": 9, - "col": 30, - "weight": 3 - }, - { - "row": 9, - "col": 31, - "weight": 2 - }, - { - "row": 9, - "col": 32, - "weight": 4 - }, - { - "row": 9, - "col": 33, - "weight": 2 - }, - { - "row": 9, - "col": 34, - "weight": 2 - }, - { - "row": 9, - "col": 35, - "weight": 2 - }, - { - "row": 9, - "col": 36, - "weight": 2 - }, - { - "row": 9, - "col": 37, - "weight": 1 - }, - { - "row": 9, - "col": 45, - "weight": 2 - }, - { - "row": 9, - "col": 47, - "weight": 2 - }, - { - "row": 9, - "col": 48, - "weight": 3 - }, - { - "row": 9, - "col": 49, - "weight": 2 - }, - { - "row": 9, - "col": 50, - "weight": 4 - }, - { - "row": 9, - "col": 51, - "weight": 2 - }, - { - "row": 9, - "col": 52, - "weight": 2 - }, - { - "row": 9, - "col": 53, - "weight": 2 - }, - { - "row": 9, - "col": 54, - "weight": 2 - }, - { - "row": 9, - "col": 55, - "weight": 2 - }, - { - "row": 9, - "col": 56, - "weight": 3 - }, - { - "row": 9, - "col": 57, - "weight": 3 - }, - { - "row": 9, - "col": 58, - "weight": 1 - }, - { - "row": 10, - "col": 9, - "weight": 2 - }, - { - "row": 10, - "col": 13, - "weight": 2 - }, - { - "row": 10, - "col": 14, - "weight": 3 - }, - { - "row": 10, - "col": 15, - "weight": 2 - }, - { - "row": 10, - "col": 24, - "weight": 2 - }, - { - "row": 10, - "col": 25, - "weight": 4 - }, - { - "row": 10, - "col": 26, - "weight": 3 - }, - { - "row": 10, - "col": 27, - "weight": 2 - }, - { - "row": 10, - "col": 30, - "weight": 2 - }, - { - "row": 10, - "col": 31, - "weight": 4 - }, - { - "row": 10, - "col": 32, - "weight": 3 - }, - { - "row": 10, - "col": 33, - "weight": 2 - }, - { - "row": 10, - "col": 44, - "weight": 2 - }, - { - "row": 10, - "col": 45, - "weight": 2 - }, - { - "row": 10, - "col": 48, - "weight": 2 - }, - { - "row": 10, - "col": 49, - "weight": 4 - }, - { - "row": 10, - "col": 50, - "weight": 3 - }, - { - "row": 10, - "col": 51, - "weight": 2 - }, - { - "row": 10, - "col": 57, - "weight": 3 - }, - { - "row": 11, - "col": 14, - "weight": 2 - }, - { - "row": 11, - "col": 24, - "weight": 2 - }, - { - "row": 11, - "col": 25, - "weight": 2 - }, - { - "row": 11, - "col": 30, - "weight": 2 - }, - { - "row": 11, - "col": 31, - "weight": 2 - }, - { - "row": 11, - "col": 44, - "weight": 2 - }, - { - "row": 11, - "col": 48, - "weight": 2 - }, - { - "row": 11, - "col": 49, - "weight": 2 - }, - { - "row": 11, - "col": 56, - "weight": 2 - }, - { - "row": 11, - "col": 57, - "weight": 2 - }, - { - "row": 12, - "col": 14, - "weight": 2 - }, - { - "row": 12, - "col": 24, - "weight": 2 - }, - { - "row": 12, - "col": 30, - "weight": 2 - }, - { - "row": 12, - "col": 44, - "weight": 2 - }, - { - "row": 12, - "col": 48, - "weight": 2 - }, - { - "row": 12, - "col": 55, - "weight": 2 - }, - { - "row": 13, - "col": 14, - "weight": 2 - }, - { - "row": 13, - "col": 25, - "weight": 2 - }, - { - "row": 13, - "col": 26, - "weight": 2 - }, - { - "row": 13, - "col": 31, - "weight": 2 - }, - { - "row": 13, - "col": 32, - "weight": 2 - }, - { - "row": 13, - "col": 44, - "weight": 2 - }, - { - "row": 13, - "col": 49, - "weight": 2 - }, - { - "row": 13, - "col": 50, - "weight": 2 - }, - { - "row": 13, - "col": 55, - "weight": 2 - }, - { - "row": 13, - "col": 56, - "weight": 2 - }, - { - "row": 14, - "col": 14, - "weight": 2 - }, - { - "row": 14, - "col": 26, - "weight": 3 - }, - { - "row": 14, - "col": 32, - "weight": 3 - }, - { - "row": 14, - "col": 44, - "weight": 3 - }, - { - "row": 14, - "col": 50, - "weight": 2 - }, - { - "row": 14, - "col": 56, - "weight": 2 - }, - { - "row": 15, - "col": 14, - "weight": 2 - }, - { - "row": 15, - "col": 16, - "weight": 2 - }, - { - "row": 15, - "col": 17, - "weight": 2 - }, - { - "row": 15, - "col": 18, - "weight": 2 - }, - { - "row": 15, - "col": 20, - "weight": 2 - }, - { - "row": 15, - "col": 22, - "weight": 2 - }, - { - "row": 15, - "col": 23, - "weight": 2 - }, - { - "row": 15, - "col": 24, - "weight": 3 - }, - { - "row": 15, - "col": 25, - "weight": 2 - }, - { - "row": 15, - "col": 26, - "weight": 4 - }, - { - "row": 15, - "col": 27, - "weight": 2 - }, - { - "row": 15, - "col": 28, - "weight": 2 - }, - { - "row": 15, - "col": 29, - "weight": 2 - }, - { - "row": 15, - "col": 30, - "weight": 3 - }, - { - "row": 15, - "col": 31, - "weight": 2 - }, - { - "row": 15, - "col": 32, - "weight": 4 - }, - { - "row": 15, - "col": 33, - "weight": 2 - }, - { - "row": 15, - "col": 34, - "weight": 2 - }, - { - "row": 15, - "col": 35, - "weight": 2 - }, - { - "row": 15, - "col": 36, - "weight": 2 - }, - { - "row": 15, - "col": 37, - "weight": 2 - }, - { - "row": 15, - "col": 38, - "weight": 2 - }, - { - "row": 15, - "col": 39, - "weight": 2 - }, - { - "row": 15, - "col": 40, - "weight": 2 - }, - { - "row": 15, - "col": 41, - "weight": 2 - }, - { - "row": 15, - "col": 42, - "weight": 2 - }, - { - "row": 15, - "col": 43, - "weight": 2 - }, - { - "row": 15, - "col": 44, - "weight": 3 - }, - { - "row": 15, - "col": 45, - "weight": 3 - }, - { - "row": 15, - "col": 46, - "weight": 1 - }, - { - "row": 15, - "col": 50, - "weight": 2 - }, - { - "row": 15, - "col": 56, - "weight": 2 - }, - { - "row": 16, - "col": 15, - "weight": 2 - }, - { - "row": 16, - "col": 19, - "weight": 2 - }, - { - "row": 16, - "col": 20, - "weight": 3 - }, - { - "row": 16, - "col": 21, - "weight": 2 - }, - { - "row": 16, - "col": 24, - "weight": 2 - }, - { - "row": 16, - "col": 25, - "weight": 4 - }, - { - "row": 16, - "col": 26, - "weight": 3 - }, - { - "row": 16, - "col": 27, - "weight": 2 - }, - { - "row": 16, - "col": 30, - "weight": 2 - }, - { - "row": 16, - "col": 31, - "weight": 4 - }, - { - "row": 16, - "col": 32, - "weight": 3 - }, - { - "row": 16, - "col": 33, - "weight": 2 - }, - { - "row": 16, - "col": 45, - "weight": 3 - }, - { - "row": 16, - "col": 50, - "weight": 2 - }, - { - "row": 16, - "col": 56, - "weight": 2 - }, - { - "row": 17, - "col": 20, - "weight": 2 - }, - { - "row": 17, - "col": 24, - "weight": 2 - }, - { - "row": 17, - "col": 25, - "weight": 2 - }, - { - "row": 17, - "col": 30, - "weight": 2 - }, - { - "row": 17, - "col": 31, - "weight": 2 - }, - { - "row": 17, - "col": 44, - "weight": 2 - }, - { - "row": 17, - "col": 45, - "weight": 2 - }, - { - "row": 17, - "col": 50, - "weight": 2 - }, - { - "row": 17, - "col": 56, - "weight": 2 - }, - { - "row": 18, - "col": 20, - "weight": 2 - }, - { - "row": 18, - "col": 24, - "weight": 2 - }, - { - "row": 18, - "col": 30, - "weight": 2 - }, - { - "row": 18, - "col": 43, - "weight": 2 - }, - { - "row": 18, - "col": 50, - "weight": 2 - }, - { - "row": 18, - "col": 56, - "weight": 2 - }, - { - "row": 19, - "col": 20, - "weight": 2 - }, - { - "row": 19, - "col": 25, - "weight": 2 - }, - { - "row": 19, - "col": 26, - "weight": 2 - }, - { - "row": 19, - "col": 31, - "weight": 2 - }, - { - "row": 19, - "col": 32, - "weight": 2 - }, - { - "row": 19, - "col": 43, - "weight": 2 - }, - { - "row": 19, - "col": 44, - "weight": 2 - }, - { - "row": 19, - "col": 50, - "weight": 2 - }, - { - "row": 19, - "col": 56, - "weight": 2 - }, - { - "row": 20, - "col": 20, - "weight": 2 - }, - { - "row": 20, - "col": 26, - "weight": 3 - }, - { - "row": 20, - "col": 28, - "weight": 2 - }, - { - "row": 20, - "col": 32, - "weight": 3 - }, - { - "row": 20, - "col": 44, - "weight": 3 - }, - { - "row": 20, - "col": 50, - "weight": 3 - }, - { - "row": 20, - "col": 56, - "weight": 2 - }, - { - "row": 21, - "col": 20, - "weight": 2 - }, - { - "row": 21, - "col": 22, - "weight": 2 - }, - { - "row": 21, - "col": 23, - "weight": 2 - }, - { - "row": 21, - "col": 24, - "weight": 2 - }, - { - "row": 21, - "col": 25, - "weight": 2 - }, - { - "row": 21, - "col": 26, - "weight": 3 - }, - { - "row": 21, - "col": 27, - "weight": 3 - }, - { - "row": 21, - "col": 29, - "weight": 2 - }, - { - "row": 21, - "col": 30, - "weight": 3 - }, - { - "row": 21, - "col": 31, - "weight": 2 - }, - { - "row": 21, - "col": 32, - "weight": 4 - }, - { - "row": 21, - "col": 33, - "weight": 2 - }, - { - "row": 21, - "col": 34, - "weight": 2 - }, - { - "row": 21, - "col": 35, - "weight": 2 - }, - { - "row": 21, - "col": 36, - "weight": 2 - }, - { - "row": 21, - "col": 37, - "weight": 2 - }, - { - "row": 21, - "col": 38, - "weight": 2 - }, - { - "row": 21, - "col": 39, - "weight": 2 - }, - { - "row": 21, - "col": 40, - "weight": 2 - }, - { - "row": 21, - "col": 41, - "weight": 2 - }, - { - "row": 21, - "col": 42, - "weight": 3 - }, - { - "row": 21, - "col": 43, - "weight": 2 - }, - { - "row": 21, - "col": 44, - "weight": 4 - }, - { - "row": 21, - "col": 45, - "weight": 2 - }, - { - "row": 21, - "col": 46, - "weight": 2 - }, - { - "row": 21, - "col": 47, - "weight": 2 - }, - { - "row": 21, - "col": 48, - "weight": 2 - }, - { - "row": 21, - "col": 49, - "weight": 2 - }, - { - "row": 21, - "col": 50, - "weight": 3 - }, - { - "row": 21, - "col": 51, - "weight": 3 - }, - { - "row": 21, - "col": 52, - "weight": 1 - }, - { - "row": 21, - "col": 56, - "weight": 2 - }, - { - "row": 22, - "col": 21, - "weight": 2 - }, - { - "row": 22, - "col": 27, - "weight": 2 - }, - { - "row": 22, - "col": 30, - "weight": 2 - }, - { - "row": 22, - "col": 31, - "weight": 4 - }, - { - "row": 22, - "col": 32, - "weight": 3 - }, - { - "row": 22, - "col": 33, - "weight": 2 - }, - { - "row": 22, - "col": 42, - "weight": 2 - }, - { - "row": 22, - "col": 43, - "weight": 4 - }, - { - "row": 22, - "col": 44, - "weight": 3 - }, - { - "row": 22, - "col": 45, - "weight": 2 - }, - { - "row": 22, - "col": 51, - "weight": 3 - }, - { - "row": 22, - "col": 56, - "weight": 2 - }, - { - "row": 23, - "col": 26, - "weight": 2 - }, - { - "row": 23, - "col": 27, - "weight": 2 - }, - { - "row": 23, - "col": 30, - "weight": 2 - }, - { - "row": 23, - "col": 31, - "weight": 2 - }, - { - "row": 23, - "col": 42, - "weight": 2 - }, - { - "row": 23, - "col": 43, - "weight": 2 - }, - { - "row": 23, - "col": 50, - "weight": 2 - }, - { - "row": 23, - "col": 51, - "weight": 2 - }, - { - "row": 23, - "col": 56, - "weight": 2 - }, - { - "row": 24, - "col": 25, - "weight": 2 - }, - { - "row": 24, - "col": 30, - "weight": 2 - }, - { - "row": 24, - "col": 42, - "weight": 2 - }, - { - "row": 24, - "col": 49, - "weight": 2 - }, - { - "row": 24, - "col": 56, - "weight": 2 - }, - { - "row": 25, - "col": 25, - "weight": 2 - }, - { - "row": 25, - "col": 26, - "weight": 2 - }, - { - "row": 25, - "col": 31, - "weight": 2 - }, - { - "row": 25, - "col": 32, - "weight": 2 - }, - { - "row": 25, - "col": 43, - "weight": 2 - }, - { - "row": 25, - "col": 44, - "weight": 2 - }, - { - "row": 25, - "col": 49, - "weight": 2 - }, - { - "row": 25, - "col": 50, - "weight": 2 - }, - { - "row": 25, - "col": 56, - "weight": 2 - }, - { - "row": 26, - "col": 26, - "weight": 2 - }, - { - "row": 26, - "col": 32, - "weight": 3 - }, - { - "row": 26, - "col": 44, - "weight": 3 - }, - { - "row": 26, - "col": 50, - "weight": 3 - }, - { - "row": 26, - "col": 56, - "weight": 1 - }, - { - "row": 27, - "col": 26, - "weight": 2 - }, - { - "row": 27, - "col": 28, - "weight": 2 - }, - { - "row": 27, - "col": 29, - "weight": 2 - }, - { - "row": 27, - "col": 30, - "weight": 3 - }, - { - "row": 27, - "col": 31, - "weight": 2 - }, - { - "row": 27, - "col": 32, - "weight": 4 - }, - { - "row": 27, - "col": 33, - "weight": 2 - }, - { - "row": 27, - "col": 34, - "weight": 2 - }, - { - "row": 27, - "col": 35, - "weight": 2 - }, - { - "row": 27, - "col": 36, - "weight": 2 - }, - { - "row": 27, - "col": 37, - "weight": 2 - }, - { - "row": 27, - "col": 38, - "weight": 2 - }, - { - "row": 27, - "col": 39, - "weight": 2 - }, - { - "row": 27, - "col": 40, - "weight": 2 - }, - { - "row": 27, - "col": 41, - "weight": 2 - }, - { - "row": 27, - "col": 42, - "weight": 3 - }, - { - "row": 27, - "col": 43, - "weight": 2 - }, - { - "row": 27, - "col": 44, - "weight": 4 - }, - { - "row": 27, - "col": 45, - "weight": 2 - }, - { - "row": 27, - "col": 46, - "weight": 2 - }, - { - "row": 27, - "col": 47, - "weight": 2 - }, - { - "row": 27, - "col": 48, - "weight": 3 - }, - { - "row": 27, - "col": 49, - "weight": 2 - }, - { - "row": 27, - "col": 50, - "weight": 4 - }, - { - "row": 27, - "col": 51, - "weight": 2 - }, - { - "row": 27, - "col": 52, - "weight": 2 - }, - { - "row": 27, - "col": 53, - "weight": 2 - }, - { - "row": 27, - "col": 54, - "weight": 2 - }, - { - "row": 27, - "col": 55, - "weight": 1 - }, - { - "row": 28, - "col": 27, - "weight": 2 - }, - { - "row": 28, - "col": 30, - "weight": 2 - }, - { - "row": 28, - "col": 31, - "weight": 4 - }, - { - "row": 28, - "col": 32, - "weight": 3 - }, - { - "row": 28, - "col": 33, - "weight": 2 - }, - { - "row": 28, - "col": 42, - "weight": 2 - }, - { - "row": 28, - "col": 43, - "weight": 4 - }, - { - "row": 28, - "col": 44, - "weight": 3 - }, - { - "row": 28, - "col": 45, - "weight": 2 - }, - { - "row": 28, - "col": 48, - "weight": 2 - }, - { - "row": 28, - "col": 49, - "weight": 4 - }, - { - "row": 28, - "col": 50, - "weight": 3 - }, - { - "row": 28, - "col": 51, - "weight": 2 - }, - { - "row": 29, - "col": 30, - "weight": 2 - }, - { - "row": 29, - "col": 31, - "weight": 2 - }, - { - "row": 29, - "col": 42, - "weight": 2 - }, - { - "row": 29, - "col": 43, - "weight": 2 - }, - { - "row": 29, - "col": 48, - "weight": 2 - }, - { - "row": 29, - "col": 49, - "weight": 2 - }, - { - "row": 30, - "col": 30, - "weight": 2 - }, - { - "row": 30, - "col": 42, - "weight": 2 - }, - { - "row": 30, - "col": 48, - "weight": 2 - }, - { - "row": 31, - "col": 31, - "weight": 2 - }, - { - "row": 31, - "col": 32, - "weight": 2 - }, - { - "row": 31, - "col": 43, - "weight": 2 - }, - { - "row": 31, - "col": 44, - "weight": 2 - }, - { - "row": 31, - "col": 49, - "weight": 2 - }, - { - "row": 31, - "col": 50, - "weight": 2 - }, - { - "row": 32, - "col": 32, - "weight": 2 - }, - { - "row": 32, - "col": 44, - "weight": 3 - }, - { - "row": 32, - "col": 50, - "weight": 1 - }, - { - "row": 33, - "col": 32, - "weight": 2 - }, - { - "row": 33, - "col": 34, - "weight": 2 - }, - { - "row": 33, - "col": 35, - "weight": 2 - }, - { - "row": 33, - "col": 36, - "weight": 2 - }, - { - "row": 33, - "col": 37, - "weight": 2 - }, - { - "row": 33, - "col": 38, - "weight": 2 - }, - { - "row": 33, - "col": 39, - "weight": 2 - }, - { - "row": 33, - "col": 40, - "weight": 2 - }, - { - "row": 33, - "col": 41, - "weight": 2 - }, - { - "row": 33, - "col": 42, - "weight": 2 - }, - { - "row": 33, - "col": 43, - "weight": 2 - }, - { - "row": 33, - "col": 44, - "weight": 2 - }, - { - "row": 33, - "col": 45, - "weight": 2 - }, - { - "row": 33, - "col": 46, - "weight": 2 - }, - { - "row": 33, - "col": 47, - "weight": 2 - }, - { - "row": 33, - "col": 48, - "weight": 2 - }, - { - "row": 33, - "col": 49, - "weight": 1 - }, - { - "row": 34, - "col": 33, - "weight": 2 - } - ], - "radius": 1.1, - "edges": [ - [ - 0, - 23 - ], - [ - 0, - 24 - ], - [ - 1, - 2 - ], - [ - 2, - 36 - ], - [ - 3, - 36 - ], - [ - 3, - 37 - ], - [ - 3, - 38 - ], - [ - 4, - 5 - ], - [ - 4, - 38 - ], - [ - 5, - 6 - ], - [ - 6, - 7 - ], - [ - 7, - 8 - ], - [ - 8, - 9 - ], - [ - 9, - 10 - ], - [ - 10, - 11 - ], - [ - 11, - 12 - ], - [ - 12, - 13 - ], - [ - 13, - 14 - ], - [ - 14, - 15 - ], - [ - 15, - 16 - ], - [ - 16, - 17 - ], - [ - 17, - 18 - ], - [ - 18, - 39 - ], - [ - 19, - 39 - ], - [ - 19, - 40 - ], - [ - 19, - 41 - ], - [ - 20, - 21 - ], - [ - 20, - 41 - ], - [ - 21, - 22 - ], - [ - 22, - 42 - ], - [ - 23, - 45 - ], - [ - 24, - 25 - ], - [ - 25, - 26 - ], - [ - 26, - 27 - ], - [ - 27, - 28 - ], - [ - 28, - 29 - ], - [ - 29, - 30 - ], - [ - 30, - 31 - ], - [ - 31, - 46 - ], - [ - 32, - 46 - ], - [ - 32, - 47 - ], - [ - 32, - 48 - ], - [ - 33, - 34 - ], - [ - 33, - 48 - ], - [ - 34, - 35 - ], - [ - 35, - 49 - ], - [ - 36, - 37 - ], - [ - 37, - 38 - ], - [ - 37, - 51 - ], - [ - 39, - 40 - ], - [ - 40, - 41 - ], - [ - 40, - 52 - ], - [ - 42, - 43 - ], - [ - 43, - 53 - ], - [ - 44, - 45 - ], - [ - 44, - 54 - ], - [ - 46, - 47 - ], - [ - 47, - 48 - ], - [ - 47, - 55 - ], - [ - 49, - 50 - ], - [ - 50, - 56 - ], - [ - 51, - 57 - ], - [ - 52, - 58 - ], - [ - 53, - 59 - ], - [ - 54, - 60 - ], - [ - 55, - 61 - ], - [ - 56, - 62 - ], - [ - 57, - 63 - ], - [ - 58, - 64 - ], - [ - 59, - 65 - ], - [ - 60, - 66 - ], - [ - 61, - 67 - ], - [ - 62, - 68 - ], - [ - 63, - 69 - ], - [ - 64, - 70 - ], - [ - 65, - 71 - ], - [ - 66, - 72 - ], - [ - 67, - 74 - ], - [ - 68, - 75 - ], - [ - 69, - 76 - ], - [ - 70, - 90 - ], - [ - 70, - 91 - ], - [ - 70, - 92 - ], - [ - 71, - 96 - ], - [ - 71, - 97 - ], - [ - 71, - 98 - ], - [ - 72, - 102 - ], - [ - 73, - 103 - ], - [ - 73, - 104 - ], - [ - 74, - 106 - ], - [ - 74, - 107 - ], - [ - 74, - 108 - ], - [ - 75, - 112 - ], - [ - 75, - 113 - ], - [ - 75, - 114 - ], - [ - 76, - 116 - ], - [ - 77, - 78 - ], - [ - 77, - 116 - ], - [ - 78, - 79 - ], - [ - 79, - 117 - ], - [ - 80, - 117 - ], - [ - 80, - 118 - ], - [ - 80, - 119 - ], - [ - 81, - 82 - ], - [ - 81, - 119 - ], - [ - 82, - 83 - ], - [ - 83, - 84 - ], - [ - 84, - 85 - ], - [ - 85, - 86 - ], - [ - 86, - 87 - ], - [ - 87, - 88 - ], - [ - 88, - 89 - ], - [ - 89, - 90 - ], - [ - 89, - 120 - ], - [ - 89, - 121 - ], - [ - 90, - 91 - ], - [ - 90, - 121 - ], - [ - 91, - 92 - ], - [ - 91, - 121 - ], - [ - 91, - 122 - ], - [ - 91, - 123 - ], - [ - 92, - 93 - ], - [ - 92, - 123 - ], - [ - 93, - 94 - ], - [ - 93, - 123 - ], - [ - 94, - 95 - ], - [ - 95, - 96 - ], - [ - 95, - 124 - ], - [ - 95, - 125 - ], - [ - 96, - 97 - ], - [ - 96, - 125 - ], - [ - 97, - 98 - ], - [ - 97, - 125 - ], - [ - 97, - 126 - ], - [ - 97, - 127 - ], - [ - 98, - 99 - ], - [ - 98, - 127 - ], - [ - 99, - 100 - ], - [ - 99, - 127 - ], - [ - 100, - 101 - ], - [ - 101, - 102 - ], - [ - 103, - 129 - ], - [ - 104, - 105 - ], - [ - 105, - 106 - ], - [ - 105, - 130 - ], - [ - 105, - 131 - ], - [ - 106, - 107 - ], - [ - 106, - 131 - ], - [ - 107, - 108 - ], - [ - 107, - 131 - ], - [ - 107, - 132 - ], - [ - 107, - 133 - ], - [ - 108, - 109 - ], - [ - 108, - 133 - ], - [ - 109, - 110 - ], - [ - 109, - 133 - ], - [ - 110, - 111 - ], - [ - 111, - 112 - ], - [ - 112, - 113 - ], - [ - 113, - 114 - ], - [ - 113, - 134 - ], - [ - 114, - 115 - ], - [ - 114, - 134 - ], - [ - 115, - 134 - ], - [ - 117, - 118 - ], - [ - 118, - 119 - ], - [ - 118, - 135 - ], - [ - 120, - 121 - ], - [ - 120, - 136 - ], - [ - 120, - 137 - ], - [ - 121, - 122 - ], - [ - 121, - 137 - ], - [ - 122, - 123 - ], - [ - 122, - 137 - ], - [ - 124, - 125 - ], - [ - 124, - 138 - ], - [ - 124, - 139 - ], - [ - 125, - 126 - ], - [ - 125, - 139 - ], - [ - 126, - 127 - ], - [ - 126, - 139 - ], - [ - 128, - 129 - ], - [ - 128, - 140 - ], - [ - 130, - 131 - ], - [ - 130, - 141 - ], - [ - 130, - 142 - ], - [ - 131, - 132 - ], - [ - 131, - 142 - ], - [ - 132, - 133 - ], - [ - 132, - 142 - ], - [ - 134, - 144 - ], - [ - 135, - 145 - ], - [ - 136, - 137 - ], - [ - 136, - 146 - ], - [ - 138, - 139 - ], - [ - 138, - 147 - ], - [ - 140, - 148 - ], - [ - 141, - 142 - ], - [ - 141, - 149 - ], - [ - 143, - 144 - ], - [ - 143, - 150 - ], - [ - 145, - 151 - ], - [ - 146, - 152 - ], - [ - 147, - 154 - ], - [ - 148, - 156 - ], - [ - 149, - 157 - ], - [ - 150, - 159 - ], - [ - 151, - 161 - ], - [ - 152, - 153 - ], - [ - 153, - 162 - ], - [ - 154, - 155 - ], - [ - 155, - 163 - ], - [ - 156, - 164 - ], - [ - 157, - 158 - ], - [ - 158, - 165 - ], - [ - 159, - 160 - ], - [ - 160, - 166 - ], - [ - 161, - 167 - ], - [ - 162, - 175 - ], - [ - 162, - 176 - ], - [ - 162, - 177 - ], - [ - 163, - 181 - ], - [ - 163, - 182 - ], - [ - 163, - 183 - ], - [ - 164, - 193 - ], - [ - 164, - 194 - ], - [ - 164, - 195 - ], - [ - 165, - 197 - ], - [ - 166, - 198 - ], - [ - 167, - 199 - ], - [ - 168, - 169 - ], - [ - 168, - 199 - ], - [ - 169, - 170 - ], - [ - 170, - 200 - ], - [ - 171, - 200 - ], - [ - 171, - 201 - ], - [ - 171, - 202 - ], - [ - 172, - 173 - ], - [ - 172, - 202 - ], - [ - 173, - 174 - ], - [ - 174, - 175 - ], - [ - 174, - 203 - ], - [ - 174, - 204 - ], - [ - 175, - 176 - ], - [ - 175, - 204 - ], - [ - 176, - 177 - ], - [ - 176, - 204 - ], - [ - 176, - 205 - ], - [ - 176, - 206 - ], - [ - 177, - 178 - ], - [ - 177, - 206 - ], - [ - 178, - 179 - ], - [ - 178, - 206 - ], - [ - 179, - 180 - ], - [ - 180, - 181 - ], - [ - 180, - 207 - ], - [ - 180, - 208 - ], - [ - 181, - 182 - ], - [ - 181, - 208 - ], - [ - 182, - 183 - ], - [ - 182, - 208 - ], - [ - 182, - 209 - ], - [ - 182, - 210 - ], - [ - 183, - 184 - ], - [ - 183, - 210 - ], - [ - 184, - 185 - ], - [ - 184, - 210 - ], - [ - 185, - 186 - ], - [ - 186, - 187 - ], - [ - 187, - 188 - ], - [ - 188, - 189 - ], - [ - 189, - 190 - ], - [ - 190, - 191 - ], - [ - 191, - 192 - ], - [ - 192, - 193 - ], - [ - 193, - 194 - ], - [ - 194, - 195 - ], - [ - 194, - 211 - ], - [ - 195, - 196 - ], - [ - 195, - 211 - ], - [ - 196, - 211 - ], - [ - 197, - 212 - ], - [ - 198, - 213 - ], - [ - 200, - 201 - ], - [ - 201, - 202 - ], - [ - 201, - 214 - ], - [ - 203, - 204 - ], - [ - 203, - 215 - ], - [ - 203, - 216 - ], - [ - 204, - 205 - ], - [ - 204, - 216 - ], - [ - 205, - 206 - ], - [ - 205, - 216 - ], - [ - 207, - 208 - ], - [ - 207, - 217 - ], - [ - 207, - 218 - ], - [ - 208, - 209 - ], - [ - 208, - 218 - ], - [ - 209, - 210 - ], - [ - 209, - 218 - ], - [ - 211, - 220 - ], - [ - 212, - 221 - ], - [ - 213, - 222 - ], - [ - 214, - 223 - ], - [ - 215, - 216 - ], - [ - 215, - 224 - ], - [ - 217, - 218 - ], - [ - 217, - 225 - ], - [ - 219, - 220 - ], - [ - 219, - 226 - ], - [ - 221, - 227 - ], - [ - 222, - 228 - ], - [ - 223, - 229 - ], - [ - 224, - 230 - ], - [ - 225, - 232 - ], - [ - 226, - 234 - ], - [ - 227, - 236 - ], - [ - 228, - 237 - ], - [ - 229, - 238 - ], - [ - 230, - 231 - ], - [ - 231, - 239 - ], - [ - 232, - 233 - ], - [ - 233, - 241 - ], - [ - 234, - 235 - ], - [ - 235, - 242 - ], - [ - 236, - 243 - ], - [ - 237, - 244 - ], - [ - 238, - 245 - ], - [ - 239, - 249 - ], - [ - 239, - 250 - ], - [ - 239, - 251 - ], - [ - 240, - 251 - ], - [ - 240, - 252 - ], - [ - 241, - 254 - ], - [ - 241, - 255 - ], - [ - 241, - 256 - ], - [ - 242, - 266 - ], - [ - 242, - 267 - ], - [ - 242, - 268 - ], - [ - 243, - 272 - ], - [ - 243, - 273 - ], - [ - 243, - 274 - ], - [ - 244, - 276 - ], - [ - 245, - 277 - ], - [ - 246, - 247 - ], - [ - 246, - 277 - ], - [ - 247, - 248 - ], - [ - 248, - 249 - ], - [ - 249, - 250 - ], - [ - 250, - 251 - ], - [ - 250, - 278 - ], - [ - 251, - 278 - ], - [ - 252, - 253 - ], - [ - 253, - 254 - ], - [ - 253, - 279 - ], - [ - 253, - 280 - ], - [ - 254, - 255 - ], - [ - 254, - 280 - ], - [ - 255, - 256 - ], - [ - 255, - 280 - ], - [ - 255, - 281 - ], - [ - 255, - 282 - ], - [ - 256, - 257 - ], - [ - 256, - 282 - ], - [ - 257, - 258 - ], - [ - 257, - 282 - ], - [ - 258, - 259 - ], - [ - 259, - 260 - ], - [ - 260, - 261 - ], - [ - 261, - 262 - ], - [ - 262, - 263 - ], - [ - 263, - 264 - ], - [ - 264, - 265 - ], - [ - 265, - 266 - ], - [ - 265, - 283 - ], - [ - 265, - 284 - ], - [ - 266, - 267 - ], - [ - 266, - 284 - ], - [ - 267, - 268 - ], - [ - 267, - 284 - ], - [ - 267, - 285 - ], - [ - 267, - 286 - ], - [ - 268, - 269 - ], - [ - 268, - 286 - ], - [ - 269, - 270 - ], - [ - 269, - 286 - ], - [ - 270, - 271 - ], - [ - 271, - 272 - ], - [ - 272, - 273 - ], - [ - 273, - 274 - ], - [ - 273, - 287 - ], - [ - 274, - 275 - ], - [ - 274, - 287 - ], - [ - 275, - 287 - ], - [ - 276, - 288 - ], - [ - 278, - 290 - ], - [ - 279, - 280 - ], - [ - 279, - 291 - ], - [ - 279, - 292 - ], - [ - 280, - 281 - ], - [ - 280, - 292 - ], - [ - 281, - 282 - ], - [ - 281, - 292 - ], - [ - 283, - 284 - ], - [ - 283, - 293 - ], - [ - 283, - 294 - ], - [ - 284, - 285 - ], - [ - 284, - 294 - ], - [ - 285, - 286 - ], - [ - 285, - 294 - ], - [ - 287, - 296 - ], - [ - 288, - 297 - ], - [ - 289, - 290 - ], - [ - 289, - 298 - ], - [ - 291, - 292 - ], - [ - 291, - 299 - ], - [ - 293, - 294 - ], - [ - 293, - 300 - ], - [ - 295, - 296 - ], - [ - 295, - 301 - ], - [ - 297, - 302 - ], - [ - 298, - 303 - ], - [ - 299, - 305 - ], - [ - 300, - 307 - ], - [ - 301, - 309 - ], - [ - 302, - 311 - ], - [ - 303, - 304 - ], - [ - 304, - 312 - ], - [ - 305, - 306 - ], - [ - 306, - 313 - ], - [ - 307, - 308 - ], - [ - 308, - 314 - ], - [ - 309, - 310 - ], - [ - 310, - 315 - ], - [ - 311, - 316 - ], - [ - 312, - 317 - ], - [ - 313, - 321 - ], - [ - 313, - 322 - ], - [ - 313, - 323 - ], - [ - 314, - 333 - ], - [ - 314, - 334 - ], - [ - 314, - 335 - ], - [ - 315, - 339 - ], - [ - 315, - 340 - ], - [ - 315, - 341 - ], - [ - 316, - 345 - ], - [ - 317, - 346 - ], - [ - 318, - 319 - ], - [ - 318, - 346 - ], - [ - 319, - 320 - ], - [ - 320, - 321 - ], - [ - 320, - 347 - ], - [ - 320, - 348 - ], - [ - 321, - 322 - ], - [ - 321, - 348 - ], - [ - 322, - 323 - ], - [ - 322, - 348 - ], - [ - 322, - 349 - ], - [ - 322, - 350 - ], - [ - 323, - 324 - ], - [ - 323, - 350 - ], - [ - 324, - 325 - ], - [ - 324, - 350 - ], - [ - 325, - 326 - ], - [ - 326, - 327 - ], - [ - 327, - 328 - ], - [ - 328, - 329 - ], - [ - 329, - 330 - ], - [ - 330, - 331 - ], - [ - 331, - 332 - ], - [ - 332, - 333 - ], - [ - 332, - 351 - ], - [ - 332, - 352 - ], - [ - 333, - 334 - ], - [ - 333, - 352 - ], - [ - 334, - 335 - ], - [ - 334, - 352 - ], - [ - 334, - 353 - ], - [ - 334, - 354 - ], - [ - 335, - 336 - ], - [ - 335, - 354 - ], - [ - 336, - 337 - ], - [ - 336, - 354 - ], - [ - 337, - 338 - ], - [ - 338, - 339 - ], - [ - 338, - 355 - ], - [ - 338, - 356 - ], - [ - 339, - 340 - ], - [ - 339, - 356 - ], - [ - 340, - 341 - ], - [ - 340, - 356 - ], - [ - 340, - 357 - ], - [ - 340, - 358 - ], - [ - 341, - 342 - ], - [ - 341, - 358 - ], - [ - 342, - 343 - ], - [ - 342, - 358 - ], - [ - 343, - 344 - ], - [ - 344, - 345 - ], - [ - 347, - 348 - ], - [ - 347, - 359 - ], - [ - 347, - 360 - ], - [ - 348, - 349 - ], - [ - 348, - 360 - ], - [ - 349, - 350 - ], - [ - 349, - 360 - ], - [ - 351, - 352 - ], - [ - 351, - 361 - ], - [ - 351, - 362 - ], - [ - 352, - 353 - ], - [ - 352, - 362 - ], - [ - 353, - 354 - ], - [ - 353, - 362 - ], - [ - 355, - 356 - ], - [ - 355, - 363 - ], - [ - 355, - 364 - ], - [ - 356, - 357 - ], - [ - 356, - 364 - ], - [ - 357, - 358 - ], - [ - 357, - 364 - ], - [ - 359, - 360 - ], - [ - 359, - 365 - ], - [ - 361, - 362 - ], - [ - 361, - 366 - ], - [ - 363, - 364 - ], - [ - 363, - 367 - ], - [ - 365, - 368 - ], - [ - 366, - 370 - ], - [ - 367, - 372 - ], - [ - 368, - 369 - ], - [ - 369, - 374 - ], - [ - 370, - 371 - ], - [ - 371, - 375 - ], - [ - 372, - 373 - ], - [ - 373, - 376 - ], - [ - 374, - 377 - ], - [ - 375, - 387 - ], - [ - 375, - 388 - ], - [ - 375, - 389 - ], - [ - 376, - 393 - ], - [ - 377, - 394 - ], - [ - 378, - 379 - ], - [ - 378, - 394 - ], - [ - 379, - 380 - ], - [ - 380, - 381 - ], - [ - 381, - 382 - ], - [ - 382, - 383 - ], - [ - 383, - 384 - ], - [ - 384, - 385 - ], - [ - 385, - 386 - ], - [ - 386, - 387 - ], - [ - 387, - 388 - ], - [ - 388, - 389 - ], - [ - 389, - 390 - ], - [ - 390, - 391 - ], - [ - 391, - 392 - ], - [ - 392, - 393 - ] - ] - }, - "mis_overhead": 375, - "padding": 2, - "spacing": 6, - "weighted": true -} \ No newline at end of file +{"nodes":[{"row":2,"col":40,"weight":2},{"row":3,"col":5,"weight":1},{"row":3,"col":6,"weight":2},{"row":3,"col":8,"weight":2},{"row":3,"col":10,"weight":2},{"row":3,"col":11,"weight":2},{"row":3,"col":12,"weight":2},{"row":3,"col":13,"weight":2},{"row":3,"col":14,"weight":2},{"row":3,"col":15,"weight":2},{"row":3,"col":16,"weight":2},{"row":3,"col":17,"weight":2},{"row":3,"col":18,"weight":2},{"row":3,"col":19,"weight":2},{"row":3,"col":20,"weight":2},{"row":3,"col":21,"weight":2},{"row":3,"col":22,"weight":2},{"row":3,"col":23,"weight":2},{"row":3,"col":24,"weight":2},{"row":3,"col":26,"weight":2},{"row":3,"col":28,"weight":2},{"row":3,"col":29,"weight":2},{"row":3,"col":30,"weight":2},{"row":3,"col":39,"weight":2},{"row":3,"col":41,"weight":2},{"row":3,"col":42,"weight":2},{"row":3,"col":43,"weight":2},{"row":3,"col":44,"weight":2},{"row":3,"col":45,"weight":2},{"row":3,"col":46,"weight":2},{"row":3,"col":47,"weight":2},{"row":3,"col":48,"weight":2},{"row":3,"col":50,"weight":2},{"row":3,"col":52,"weight":2},{"row":3,"col":53,"weight":2},{"row":3,"col":54,"weight":2},{"row":4,"col":7,"weight":2},{"row":4,"col":8,"weight":3},{"row":4,"col":9,"weight":2},{"row":4,"col":25,"weight":2},{"row":4,"col":26,"weight":3},{"row":4,"col":27,"weight":2},{"row":4,"col":31,"weight":1},{"row":4,"col":32,"weight":1},{"row":4,"col":38,"weight":2},{"row":4,"col":39,"weight":2},{"row":4,"col":49,"weight":2},{"row":4,"col":50,"weight":3},{"row":4,"col":51,"weight":2},{"row":4,"col":55,"weight":1},{"row":4,"col":56,"weight":1},{"row":5,"col":8,"weight":2},{"row":5,"col":26,"weight":2},{"row":5,"col":32,"weight":2},{"row":5,"col":38,"weight":2},{"row":5,"col":50,"weight":2},{"row":5,"col":56,"weight":2},{"row":6,"col":8,"weight":2},{"row":6,"col":26,"weight":2},{"row":6,"col":32,"weight":2},{"row":6,"col":38,"weight":2},{"row":6,"col":50,"weight":2},{"row":6,"col":56,"weight":2},{"row":7,"col":8,"weight":2},{"row":7,"col":26,"weight":2},{"row":7,"col":32,"weight":2},{"row":7,"col":38,"weight":2},{"row":7,"col":50,"weight":2},{"row":7,"col":56,"weight":2},{"row":8,"col":8,"weight":2},{"row":8,"col":26,"weight":3},{"row":8,"col":32,"weight":3},{"row":8,"col":38,"weight":1},{"row":8,"col":46,"weight":2},{"row":8,"col":50,"weight":3},{"row":8,"col":56,"weight":3},{"row":9,"col":8,"weight":2},{"row":9,"col":10,"weight":2},{"row":9,"col":11,"weight":2},{"row":9,"col":12,"weight":2},{"row":9,"col":14,"weight":2},{"row":9,"col":16,"weight":2},{"row":9,"col":17,"weight":2},{"row":9,"col":18,"weight":2},{"row":9,"col":19,"weight":2},{"row":9,"col":20,"weight":2},{"row":9,"col":21,"weight":2},{"row":9,"col":22,"weight":2},{"row":9,"col":23,"weight":2},{"row":9,"col":24,"weight":3},{"row":9,"col":25,"weight":2},{"row":9,"col":26,"weight":4},{"row":9,"col":27,"weight":2},{"row":9,"col":28,"weight":2},{"row":9,"col":29,"weight":2},{"row":9,"col":30,"weight":3},{"row":9,"col":31,"weight":2},{"row":9,"col":32,"weight":4},{"row":9,"col":33,"weight":2},{"row":9,"col":34,"weight":2},{"row":9,"col":35,"weight":2},{"row":9,"col":36,"weight":2},{"row":9,"col":37,"weight":1},{"row":9,"col":45,"weight":2},{"row":9,"col":47,"weight":2},{"row":9,"col":48,"weight":3},{"row":9,"col":49,"weight":2},{"row":9,"col":50,"weight":4},{"row":9,"col":51,"weight":2},{"row":9,"col":52,"weight":2},{"row":9,"col":53,"weight":2},{"row":9,"col":54,"weight":2},{"row":9,"col":55,"weight":2},{"row":9,"col":56,"weight":3},{"row":9,"col":57,"weight":3},{"row":9,"col":58,"weight":1},{"row":10,"col":9,"weight":2},{"row":10,"col":13,"weight":2},{"row":10,"col":14,"weight":3},{"row":10,"col":15,"weight":2},{"row":10,"col":24,"weight":2},{"row":10,"col":25,"weight":4},{"row":10,"col":26,"weight":3},{"row":10,"col":27,"weight":2},{"row":10,"col":30,"weight":2},{"row":10,"col":31,"weight":4},{"row":10,"col":32,"weight":3},{"row":10,"col":33,"weight":2},{"row":10,"col":44,"weight":2},{"row":10,"col":45,"weight":2},{"row":10,"col":48,"weight":2},{"row":10,"col":49,"weight":4},{"row":10,"col":50,"weight":3},{"row":10,"col":51,"weight":2},{"row":10,"col":57,"weight":3},{"row":11,"col":14,"weight":2},{"row":11,"col":24,"weight":2},{"row":11,"col":25,"weight":2},{"row":11,"col":30,"weight":2},{"row":11,"col":31,"weight":2},{"row":11,"col":44,"weight":2},{"row":11,"col":48,"weight":2},{"row":11,"col":49,"weight":2},{"row":11,"col":56,"weight":2},{"row":11,"col":57,"weight":2},{"row":12,"col":14,"weight":2},{"row":12,"col":24,"weight":2},{"row":12,"col":30,"weight":2},{"row":12,"col":44,"weight":2},{"row":12,"col":48,"weight":2},{"row":12,"col":55,"weight":2},{"row":13,"col":14,"weight":2},{"row":13,"col":25,"weight":2},{"row":13,"col":26,"weight":2},{"row":13,"col":31,"weight":2},{"row":13,"col":32,"weight":2},{"row":13,"col":44,"weight":2},{"row":13,"col":49,"weight":2},{"row":13,"col":50,"weight":2},{"row":13,"col":55,"weight":2},{"row":13,"col":56,"weight":2},{"row":14,"col":14,"weight":2},{"row":14,"col":26,"weight":3},{"row":14,"col":32,"weight":3},{"row":14,"col":44,"weight":3},{"row":14,"col":50,"weight":2},{"row":14,"col":56,"weight":2},{"row":15,"col":14,"weight":2},{"row":15,"col":16,"weight":2},{"row":15,"col":17,"weight":2},{"row":15,"col":18,"weight":2},{"row":15,"col":20,"weight":2},{"row":15,"col":22,"weight":2},{"row":15,"col":23,"weight":2},{"row":15,"col":24,"weight":3},{"row":15,"col":25,"weight":2},{"row":15,"col":26,"weight":4},{"row":15,"col":27,"weight":2},{"row":15,"col":28,"weight":2},{"row":15,"col":29,"weight":2},{"row":15,"col":30,"weight":3},{"row":15,"col":31,"weight":2},{"row":15,"col":32,"weight":4},{"row":15,"col":33,"weight":2},{"row":15,"col":34,"weight":2},{"row":15,"col":35,"weight":2},{"row":15,"col":36,"weight":2},{"row":15,"col":37,"weight":2},{"row":15,"col":38,"weight":2},{"row":15,"col":39,"weight":2},{"row":15,"col":40,"weight":2},{"row":15,"col":41,"weight":2},{"row":15,"col":42,"weight":2},{"row":15,"col":43,"weight":2},{"row":15,"col":44,"weight":3},{"row":15,"col":45,"weight":3},{"row":15,"col":46,"weight":1},{"row":15,"col":50,"weight":2},{"row":15,"col":56,"weight":2},{"row":16,"col":15,"weight":2},{"row":16,"col":19,"weight":2},{"row":16,"col":20,"weight":3},{"row":16,"col":21,"weight":2},{"row":16,"col":24,"weight":2},{"row":16,"col":25,"weight":4},{"row":16,"col":26,"weight":3},{"row":16,"col":27,"weight":2},{"row":16,"col":30,"weight":2},{"row":16,"col":31,"weight":4},{"row":16,"col":32,"weight":3},{"row":16,"col":33,"weight":2},{"row":16,"col":45,"weight":3},{"row":16,"col":50,"weight":2},{"row":16,"col":56,"weight":2},{"row":17,"col":20,"weight":2},{"row":17,"col":24,"weight":2},{"row":17,"col":25,"weight":2},{"row":17,"col":30,"weight":2},{"row":17,"col":31,"weight":2},{"row":17,"col":44,"weight":2},{"row":17,"col":45,"weight":2},{"row":17,"col":50,"weight":2},{"row":17,"col":56,"weight":2},{"row":18,"col":20,"weight":2},{"row":18,"col":24,"weight":2},{"row":18,"col":30,"weight":2},{"row":18,"col":43,"weight":2},{"row":18,"col":50,"weight":2},{"row":18,"col":56,"weight":2},{"row":19,"col":20,"weight":2},{"row":19,"col":25,"weight":2},{"row":19,"col":26,"weight":2},{"row":19,"col":31,"weight":2},{"row":19,"col":32,"weight":2},{"row":19,"col":43,"weight":2},{"row":19,"col":44,"weight":2},{"row":19,"col":50,"weight":2},{"row":19,"col":56,"weight":2},{"row":20,"col":20,"weight":2},{"row":20,"col":26,"weight":3},{"row":20,"col":28,"weight":2},{"row":20,"col":32,"weight":3},{"row":20,"col":44,"weight":3},{"row":20,"col":50,"weight":3},{"row":20,"col":56,"weight":2},{"row":21,"col":20,"weight":2},{"row":21,"col":22,"weight":2},{"row":21,"col":23,"weight":2},{"row":21,"col":24,"weight":2},{"row":21,"col":25,"weight":2},{"row":21,"col":26,"weight":3},{"row":21,"col":27,"weight":3},{"row":21,"col":29,"weight":2},{"row":21,"col":30,"weight":3},{"row":21,"col":31,"weight":2},{"row":21,"col":32,"weight":4},{"row":21,"col":33,"weight":2},{"row":21,"col":34,"weight":2},{"row":21,"col":35,"weight":2},{"row":21,"col":36,"weight":2},{"row":21,"col":37,"weight":2},{"row":21,"col":38,"weight":2},{"row":21,"col":39,"weight":2},{"row":21,"col":40,"weight":2},{"row":21,"col":41,"weight":2},{"row":21,"col":42,"weight":3},{"row":21,"col":43,"weight":2},{"row":21,"col":44,"weight":4},{"row":21,"col":45,"weight":2},{"row":21,"col":46,"weight":2},{"row":21,"col":47,"weight":2},{"row":21,"col":48,"weight":2},{"row":21,"col":49,"weight":2},{"row":21,"col":50,"weight":3},{"row":21,"col":51,"weight":3},{"row":21,"col":52,"weight":1},{"row":21,"col":56,"weight":2},{"row":22,"col":21,"weight":2},{"row":22,"col":27,"weight":2},{"row":22,"col":30,"weight":2},{"row":22,"col":31,"weight":4},{"row":22,"col":32,"weight":3},{"row":22,"col":33,"weight":2},{"row":22,"col":42,"weight":2},{"row":22,"col":43,"weight":4},{"row":22,"col":44,"weight":3},{"row":22,"col":45,"weight":2},{"row":22,"col":51,"weight":3},{"row":22,"col":56,"weight":2},{"row":23,"col":26,"weight":2},{"row":23,"col":27,"weight":2},{"row":23,"col":30,"weight":2},{"row":23,"col":31,"weight":2},{"row":23,"col":42,"weight":2},{"row":23,"col":43,"weight":2},{"row":23,"col":50,"weight":2},{"row":23,"col":51,"weight":2},{"row":23,"col":56,"weight":2},{"row":24,"col":25,"weight":2},{"row":24,"col":30,"weight":2},{"row":24,"col":42,"weight":2},{"row":24,"col":49,"weight":2},{"row":24,"col":56,"weight":2},{"row":25,"col":25,"weight":2},{"row":25,"col":26,"weight":2},{"row":25,"col":31,"weight":2},{"row":25,"col":32,"weight":2},{"row":25,"col":43,"weight":2},{"row":25,"col":44,"weight":2},{"row":25,"col":49,"weight":2},{"row":25,"col":50,"weight":2},{"row":25,"col":56,"weight":2},{"row":26,"col":26,"weight":2},{"row":26,"col":32,"weight":3},{"row":26,"col":44,"weight":3},{"row":26,"col":50,"weight":3},{"row":26,"col":56,"weight":1},{"row":27,"col":26,"weight":2},{"row":27,"col":28,"weight":2},{"row":27,"col":29,"weight":2},{"row":27,"col":30,"weight":3},{"row":27,"col":31,"weight":2},{"row":27,"col":32,"weight":4},{"row":27,"col":33,"weight":2},{"row":27,"col":34,"weight":2},{"row":27,"col":35,"weight":2},{"row":27,"col":36,"weight":2},{"row":27,"col":37,"weight":2},{"row":27,"col":38,"weight":2},{"row":27,"col":39,"weight":2},{"row":27,"col":40,"weight":2},{"row":27,"col":41,"weight":2},{"row":27,"col":42,"weight":3},{"row":27,"col":43,"weight":2},{"row":27,"col":44,"weight":4},{"row":27,"col":45,"weight":2},{"row":27,"col":46,"weight":2},{"row":27,"col":47,"weight":2},{"row":27,"col":48,"weight":3},{"row":27,"col":49,"weight":2},{"row":27,"col":50,"weight":4},{"row":27,"col":51,"weight":2},{"row":27,"col":52,"weight":2},{"row":27,"col":53,"weight":2},{"row":27,"col":54,"weight":2},{"row":27,"col":55,"weight":1},{"row":28,"col":27,"weight":2},{"row":28,"col":30,"weight":2},{"row":28,"col":31,"weight":4},{"row":28,"col":32,"weight":3},{"row":28,"col":33,"weight":2},{"row":28,"col":42,"weight":2},{"row":28,"col":43,"weight":4},{"row":28,"col":44,"weight":3},{"row":28,"col":45,"weight":2},{"row":28,"col":48,"weight":2},{"row":28,"col":49,"weight":4},{"row":28,"col":50,"weight":3},{"row":28,"col":51,"weight":2},{"row":29,"col":30,"weight":2},{"row":29,"col":31,"weight":2},{"row":29,"col":42,"weight":2},{"row":29,"col":43,"weight":2},{"row":29,"col":48,"weight":2},{"row":29,"col":49,"weight":2},{"row":30,"col":30,"weight":2},{"row":30,"col":42,"weight":2},{"row":30,"col":48,"weight":2},{"row":31,"col":31,"weight":2},{"row":31,"col":32,"weight":2},{"row":31,"col":43,"weight":2},{"row":31,"col":44,"weight":2},{"row":31,"col":49,"weight":2},{"row":31,"col":50,"weight":2},{"row":32,"col":32,"weight":2},{"row":32,"col":44,"weight":3},{"row":32,"col":50,"weight":1},{"row":33,"col":32,"weight":2},{"row":33,"col":34,"weight":2},{"row":33,"col":35,"weight":2},{"row":33,"col":36,"weight":2},{"row":33,"col":37,"weight":2},{"row":33,"col":38,"weight":2},{"row":33,"col":39,"weight":2},{"row":33,"col":40,"weight":2},{"row":33,"col":41,"weight":2},{"row":33,"col":42,"weight":2},{"row":33,"col":43,"weight":2},{"row":33,"col":44,"weight":2},{"row":33,"col":45,"weight":2},{"row":33,"col":46,"weight":2},{"row":33,"col":47,"weight":2},{"row":33,"col":48,"weight":2},{"row":33,"col":49,"weight":1},{"row":34,"col":33,"weight":2}],"edges":[[0,23],[0,24],[1,2],[2,36],[3,36],[3,37],[3,38],[4,5],[4,38],[5,6],[6,7],[7,8],[8,9],[9,10],[10,11],[11,12],[12,13],[13,14],[14,15],[15,16],[16,17],[17,18],[18,39],[19,39],[19,40],[19,41],[20,21],[20,41],[21,22],[22,42],[23,45],[24,25],[25,26],[26,27],[27,28],[28,29],[29,30],[30,31],[31,46],[32,46],[32,47],[32,48],[33,34],[33,48],[34,35],[35,49],[36,37],[37,38],[37,51],[39,40],[40,41],[40,52],[42,43],[43,53],[44,45],[44,54],[46,47],[47,48],[47,55],[49,50],[50,56],[51,57],[52,58],[53,59],[54,60],[55,61],[56,62],[57,63],[58,64],[59,65],[60,66],[61,67],[62,68],[63,69],[64,70],[65,71],[66,72],[67,74],[68,75],[69,76],[70,90],[70,91],[70,92],[71,96],[71,97],[71,98],[72,102],[73,103],[73,104],[74,106],[74,107],[74,108],[75,112],[75,113],[75,114],[76,116],[77,78],[77,116],[78,79],[79,117],[80,117],[80,118],[80,119],[81,82],[81,119],[82,83],[83,84],[84,85],[85,86],[86,87],[87,88],[88,89],[89,90],[89,120],[89,121],[90,91],[90,121],[91,92],[91,121],[91,122],[91,123],[92,93],[92,123],[93,94],[93,123],[94,95],[95,96],[95,124],[95,125],[96,97],[96,125],[97,98],[97,125],[97,126],[97,127],[98,99],[98,127],[99,100],[99,127],[100,101],[101,102],[103,129],[104,105],[105,106],[105,130],[105,131],[106,107],[106,131],[107,108],[107,131],[107,132],[107,133],[108,109],[108,133],[109,110],[109,133],[110,111],[111,112],[112,113],[113,114],[113,134],[114,115],[114,134],[115,134],[117,118],[118,119],[118,135],[120,121],[120,136],[120,137],[121,122],[121,137],[122,123],[122,137],[124,125],[124,138],[124,139],[125,126],[125,139],[126,127],[126,139],[128,129],[128,140],[130,131],[130,141],[130,142],[131,132],[131,142],[132,133],[132,142],[134,144],[135,145],[136,137],[136,146],[138,139],[138,147],[140,148],[141,142],[141,149],[143,144],[143,150],[145,151],[146,152],[147,154],[148,156],[149,157],[150,159],[151,161],[152,153],[153,162],[154,155],[155,163],[156,164],[157,158],[158,165],[159,160],[160,166],[161,167],[162,175],[162,176],[162,177],[163,181],[163,182],[163,183],[164,193],[164,194],[164,195],[165,197],[166,198],[167,199],[168,169],[168,199],[169,170],[170,200],[171,200],[171,201],[171,202],[172,173],[172,202],[173,174],[174,175],[174,203],[174,204],[175,176],[175,204],[176,177],[176,204],[176,205],[176,206],[177,178],[177,206],[178,179],[178,206],[179,180],[180,181],[180,207],[180,208],[181,182],[181,208],[182,183],[182,208],[182,209],[182,210],[183,184],[183,210],[184,185],[184,210],[185,186],[186,187],[187,188],[188,189],[189,190],[190,191],[191,192],[192,193],[193,194],[194,195],[194,211],[195,196],[195,211],[196,211],[197,212],[198,213],[200,201],[201,202],[201,214],[203,204],[203,215],[203,216],[204,205],[204,216],[205,206],[205,216],[207,208],[207,217],[207,218],[208,209],[208,218],[209,210],[209,218],[211,220],[212,221],[213,222],[214,223],[215,216],[215,224],[217,218],[217,225],[219,220],[219,226],[221,227],[222,228],[223,229],[224,230],[225,232],[226,234],[227,236],[228,237],[229,238],[230,231],[231,239],[232,233],[233,241],[234,235],[235,242],[236,243],[237,244],[238,245],[239,249],[239,250],[239,251],[240,251],[240,252],[241,254],[241,255],[241,256],[242,266],[242,267],[242,268],[243,272],[243,273],[243,274],[244,276],[245,277],[246,247],[246,277],[247,248],[248,249],[249,250],[250,251],[250,278],[251,278],[252,253],[253,254],[253,279],[253,280],[254,255],[254,280],[255,256],[255,280],[255,281],[255,282],[256,257],[256,282],[257,258],[257,282],[258,259],[259,260],[260,261],[261,262],[262,263],[263,264],[264,265],[265,266],[265,283],[265,284],[266,267],[266,284],[267,268],[267,284],[267,285],[267,286],[268,269],[268,286],[269,270],[269,286],[270,271],[271,272],[272,273],[273,274],[273,287],[274,275],[274,287],[275,287],[276,288],[278,290],[279,280],[279,291],[279,292],[280,281],[280,292],[281,282],[281,292],[283,284],[283,293],[283,294],[284,285],[284,294],[285,286],[285,294],[287,296],[288,297],[289,290],[289,298],[291,292],[291,299],[293,294],[293,300],[295,296],[295,301],[297,302],[298,303],[299,305],[300,307],[301,309],[302,311],[303,304],[304,312],[305,306],[306,313],[307,308],[308,314],[309,310],[310,315],[311,316],[312,317],[313,321],[313,322],[313,323],[314,333],[314,334],[314,335],[315,339],[315,340],[315,341],[316,345],[317,346],[318,319],[318,346],[319,320],[320,321],[320,347],[320,348],[321,322],[321,348],[322,323],[322,348],[322,349],[322,350],[323,324],[323,350],[324,325],[324,350],[325,326],[326,327],[327,328],[328,329],[329,330],[330,331],[331,332],[332,333],[332,351],[332,352],[333,334],[333,352],[334,335],[334,352],[334,353],[334,354],[335,336],[335,354],[336,337],[336,354],[337,338],[338,339],[338,355],[338,356],[339,340],[339,356],[340,341],[340,356],[340,357],[340,358],[341,342],[341,358],[342,343],[342,358],[343,344],[344,345],[347,348],[347,359],[347,360],[348,349],[348,360],[349,350],[349,360],[351,352],[351,361],[351,362],[352,353],[352,362],[353,354],[353,362],[355,356],[355,363],[355,364],[356,357],[356,364],[357,358],[357,364],[359,360],[359,365],[361,362],[361,366],[363,364],[363,367],[365,368],[366,370],[367,372],[368,369],[369,374],[370,371],[371,375],[372,373],[373,376],[374,377],[375,387],[375,388],[375,389],[376,393],[377,394],[378,379],[378,394],[379,380],[380,381],[381,382],[382,383],[383,384],[384,385],[385,386],[386,387],[387,388],[388,389],[389,390],[390,391],[391,392],[392,393]],"mis_overhead":375,"padding":2,"spacing":6,"weighted":true} \ No newline at end of file diff --git a/docs/plans/2026-02-14-variant-aware-paths-design.md b/docs/plans/2026-02-14-variant-aware-paths-design.md new file mode 100644 index 00000000..452a1b81 --- /dev/null +++ b/docs/plans/2026-02-14-variant-aware-paths-design.md @@ -0,0 +1,204 @@ +# Variant-Aware Reduction Paths + +**Goal:** Make reduction paths variant-level so that (a) variant-specific reductions are disambiguated (issue 2) and (b) natural cast steps are computed automatically from subtype hierarchies (issue 5). + +## Background + +The runtime `ReductionGraph` uses name-only nodes. `ReductionPath` is `Vec<&'static str>` — it carries no variant information. This causes two problems: + +1. **Overhead lookup ambiguity (issue 2):** `lookup_overhead("KSatisfiability", "QUBO")` returns the first hit from inventory. KSatisfiability<2>→QUBO and KSatisfiability<3>→QUBO have different overheads, but the caller can't distinguish them. + +2. **Natural edge inconsistency (issue 5):** The JSON export infers 8 natural edges (e.g., MIS GridGraph→SimpleGraph) from subtype hierarchies, but only 1 has a backing `ReduceTo` impl. Users see edges in documentation that aren't executable. + +## Design + +### 1. `ResolvedPath` Data Model + +A variant-level path where each node carries `(name, variant)` and each edge is typed: + +```rust +/// A node in a variant-level reduction path. +#[derive(Debug, Clone, Serialize)] +pub struct ReductionStep { + /// Problem name (e.g., "MaximumIndependentSet"). + pub name: String, + /// Variant at this point (e.g., {"graph": "GridGraph", "weight": "i32"}). + pub variant: BTreeMap, +} + +/// The kind of transition between adjacent steps. +#[derive(Debug, Clone, Serialize)] +pub enum EdgeKind { + /// A registered reduction (backed by a ReduceTo impl). + Reduction { + /// Overhead from the matching ReductionEntry. + overhead: ReductionOverhead, + }, + /// A natural cast via subtype relaxation. Identity overhead. + NaturalCast, +} + +/// A fully resolved reduction path with variant information at each node. +#[derive(Debug, Clone, Serialize)] +pub struct ResolvedPath { + /// Sequence of (name, variant) nodes. + pub steps: Vec, + /// Edge kinds between adjacent steps. Length = steps.len() - 1. + pub edges: Vec, +} +``` + +Example — resolving `MIS(GridGraph, i32) → QUBO(f64)` through name-path `["MIS", "QUBO"]`: + +``` +steps: + [0] MIS {graph: "GridGraph", weight: "i32"} ← source + [1] MIS {graph: "SimpleGraph", weight: "i32"} ← natural cast + [2] QUBO {weight: "f64"} ← reduction target + +edges: + [0] NaturalCast ← GridGraph <: SimpleGraph + [1] Reduction { overhead: ... } ← MIS→QUBO rule +``` + +### 2. Resolution Algorithm + +```rust +impl ReductionGraph { + pub fn resolve_path( + &self, + path: &ReductionPath, + source_variant: &BTreeMap, + target_variant: &BTreeMap, + ) -> Option { ... } +} +``` + +Algorithm: + +``` +current_variant = source_variant +steps = [ Step(path[0], current_variant) ] +edges = [] + +for each edge (src_name → dst_name) in the name-level path: + + 1. FIND CANDIDATES + Collect all ReductionEntry where + entry.source_name == src_name AND entry.target_name == dst_name + + 2. FILTER COMPATIBLE + Keep entries where current_variant is reducible to entry.source_variant + (current is equal-or-more-specific on every variant axis) + + 3. PICK MOST SPECIFIC + Among compatible entries, pick the one whose source_variant is the + tightest supertype of current_variant. + If none compatible → return None. + + 4. INSERT NATURAL CAST (if needed) + If current_variant ≠ best_rule.source_variant: + steps.push( Step(src_name, best_rule.source_variant) ) + edges.push( NaturalCast ) + + 5. ADVANCE + current_variant = best_rule.target_variant + steps.push( Step(dst_name, current_variant) ) + edges.push( Reduction { overhead: best_rule.overhead() } ) + +// Trailing natural cast if final variant differs from target +if current_variant ≠ target_variant + AND is_variant_reducible(current_variant, target_variant): + steps.push( Step(last_name, target_variant) ) + edges.push( NaturalCast ) + +return ResolvedPath { steps, edges } +``` + +### 3. KSat Disambiguation Example + +Resolving `KSat(k=3) → QUBO` via name-path `["KSatisfiability", "QUBO"]`: + +``` +FIND CANDIDATES: + - KSat<2>→QUBO (source_variant: {k:"2"}, overhead: num_vars) + - KSat<3>→QUBO (source_variant: {k:"3"}, overhead: num_vars + num_clauses) + +FILTER COMPATIBLE with current k=3: + - KSat<2>: k=3 reducible to k=2? No (3 is not a subtype of 2) + - KSat<3>: k=3 == k=3? Yes ✓ + +PICK: KSat<3>→QUBO with correct overhead. +``` + +Overhead ambiguity is resolved by construction — the resolver picks the exact matching entry. + +### 4. Natural Edges Become Implicit + +With `resolve_path`, natural casts are **computed from subtype hierarchies**, not registered as `ReduceTo` impls. + +**Removed:** +- `impl_natural_reduction!` macro invocations (the one in `natural.rs` and any future ones) +- Natural edges no longer need `ReductionEntry` registration via inventory + +**Kept:** +- `GraphSubtypeEntry` / `WeightSubtypeEntry` — source of truth for subtype relationships +- Inference logic in `to_json()` — unchanged, still produces natural edges in JSON export +- `GraphCast` trait — still needed for actual execution by callers + +**Callers execute natural steps** using `GraphCast::cast_graph()` (or equivalent weight cast) directly, guided by the `EdgeKind::NaturalCast` marker in the resolved path. No `ReduceTo` dispatch needed. + +### 5. `lookup_overhead` Deprecated + +`lookup_overhead(source_name, target_name)` is replaced by per-step overhead in `ResolvedPath`: + +```rust +impl ResolvedPath { + /// Total overhead for the entire path (composed across all steps). + pub fn total_overhead(&self) -> ReductionOverhead { ... } + + /// Number of reduction steps (excludes natural casts). + pub fn num_reductions(&self) -> usize { ... } + + /// Number of natural cast steps. + pub fn num_casts(&self) -> usize { ... } +} +``` + +Examples migrate from `lookup_overhead("A", "B")` to using the resolved path's overhead. + +### 6. Backward Compatibility + +| API | Change | +|-----|--------| +| `ReductionPath` | Unchanged — still returned by `find_paths`, `find_cheapest_path` | +| `find_paths`, `find_paths_by_name` | Unchanged | +| `find_cheapest_path` | Unchanged (name-level planning) | +| `has_direct_reduction` | Unchanged | +| `resolve_path` | **New** — lifts name-level path to variant-level | +| `ResolvedPath` | **New** | +| `lookup_overhead` | **Deprecated** — kept for one release, then removed | +| `lookup_overhead_or_empty` | **Deprecated** | +| `impl_natural_reduction!` | **Removed** after migration | + +Existing code using `find_paths` + `lookup_overhead` continues working. New code should use `find_paths` + `resolve_path` for variant-correct results. + +### 7. Files Changed + +| File | Change | +|------|--------| +| `src/rules/graph.rs` | Add `ResolvedPath`, `ReductionStep`, `EdgeKind`, `resolve_path()` method | +| `src/export.rs` | Deprecate `lookup_overhead`, `lookup_overhead_or_empty` | +| `src/rules/natural.rs` | Remove `impl_natural_reduction!` invocation | +| `src/rules/mod.rs` | Keep `impl_natural_reduction!` macro (optional convenience), remove from prelude | +| `examples/reduction_ksatisfiability_to_qubo.rs` | Migrate from `lookup_overhead` to `resolve_path` | +| `examples/*.rs` | Migrate remaining examples (can be incremental) | +| `src/unit_tests/rules/graph.rs` | Add tests for `resolve_path` | +| `src/unit_tests/rules/natural.rs` | Update or remove natural reduction tests | + +### 8. Non-Goals + +- Runtime graph does not become variant-level (stays name-only for path discovery) +- No execution engine — `ResolvedPath` is a plan; callers dispatch `ReduceTo` and `GraphCast` themselves +- No changes to `to_json()` natural edge inference (it already works correctly) +- No changes to `#[reduction]` macro diff --git a/docs/plans/2026-02-14-variant-aware-paths-impl.md b/docs/plans/2026-02-14-variant-aware-paths-impl.md new file mode 100644 index 00000000..ce6beba5 --- /dev/null +++ b/docs/plans/2026-02-14-variant-aware-paths-impl.md @@ -0,0 +1,747 @@ +# Variant-Aware Reduction Paths — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add `resolve_path()` to lift name-level reduction paths into variant-level paths with natural cast steps, fixing overhead disambiguation (issue 2) and natural edge inconsistency (issue 5). + +**Architecture:** `ResolvedPath` is a new type layered on top of the existing `ReductionPath`. The resolver walks a name-level path, threads variant state through each edge, picks the most-specific matching `ReductionEntry`, and inserts `NaturalCast` steps where the caller's variant is more specific than what the rule expects. No changes to the name-level graph or path-finding algorithms. + +**Tech Stack:** Rust, `inventory` crate (existing), `petgraph` (existing), `serde` (existing), `BTreeMap` for variant representation. + +--- + +### Task 1: Add `ResolvedPath` data types + +**Files:** +- Modify: `src/rules/graph.rs` (after `ReductionPath` impl block, ~line 132) + +**Step 1: Write the failing test** + +Add to `src/unit_tests/rules/graph.rs`: + +```rust +#[test] +fn test_resolved_path_basic_structure() { + use crate::rules::graph::{ResolvedPath, ReductionStep, EdgeKind}; + use std::collections::BTreeMap; + + let steps = vec![ + ReductionStep { + name: "A".to_string(), + variant: BTreeMap::from([("graph".to_string(), "SimpleGraph".to_string())]), + }, + ReductionStep { + name: "B".to_string(), + variant: BTreeMap::from([("weight".to_string(), "f64".to_string())]), + }, + ]; + let edges = vec![EdgeKind::Reduction { + overhead: Default::default(), + }]; + let path = ResolvedPath { + steps: steps.clone(), + edges, + }; + + assert_eq!(path.len(), 1); + assert_eq!(path.num_reductions(), 1); + assert_eq!(path.num_casts(), 0); + assert_eq!(path.steps[0].name, "A"); + assert_eq!(path.steps[1].name, "B"); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `cargo test test_resolved_path_basic_structure -- --no-run 2>&1` +Expected: Compilation error — `ResolvedPath`, `ReductionStep`, `EdgeKind` not defined. + +**Step 3: Write the types** + +Add to `src/rules/graph.rs` after the `ReductionPath` impl block (after line 132): + +```rust +/// A node in a variant-level reduction path. +#[derive(Debug, Clone, Serialize)] +pub struct ReductionStep { + /// Problem name (e.g., "MaximumIndependentSet"). + pub name: String, + /// Variant at this point (e.g., {"graph": "GridGraph", "weight": "i32"}). + pub variant: std::collections::BTreeMap, +} + +/// The kind of transition between adjacent steps in a resolved path. +#[derive(Debug, Clone, Serialize)] +pub enum EdgeKind { + /// A registered reduction (backed by a ReduceTo impl). + Reduction { + /// Overhead from the matching ReductionEntry. + overhead: ReductionOverhead, + }, + /// A natural cast via subtype relaxation. Identity overhead. + NaturalCast, +} + +/// A fully resolved reduction path with variant information at each node. +/// +/// Created by [`ReductionGraph::resolve_path`] from a name-level [`ReductionPath`]. +/// Each adjacent pair of steps is connected by an [`EdgeKind`]: either a registered +/// reduction or a natural cast (subtype relaxation with identity overhead). +#[derive(Debug, Clone, Serialize)] +pub struct ResolvedPath { + /// Sequence of (name, variant) nodes. + pub steps: Vec, + /// Edge kinds between adjacent steps. Length = steps.len() - 1. + pub edges: Vec, +} + +impl ResolvedPath { + /// Number of edges (reductions + casts) in the path. + pub fn len(&self) -> usize { + self.edges.len() + } + + /// Whether the path is empty. + pub fn is_empty(&self) -> bool { + self.edges.is_empty() + } + + /// Number of registered reduction steps (excludes natural casts). + pub fn num_reductions(&self) -> usize { + self.edges + .iter() + .filter(|e| matches!(e, EdgeKind::Reduction { .. })) + .count() + } + + /// Number of natural cast steps. + pub fn num_casts(&self) -> usize { + self.edges + .iter() + .filter(|e| matches!(e, EdgeKind::NaturalCast)) + .count() + } +} +``` + +**Step 4: Run test to verify it passes** + +Run: `cargo test test_resolved_path_basic_structure` +Expected: PASS + +**Step 5: Commit** + +```bash +git add src/rules/graph.rs src/unit_tests/rules/graph.rs +git commit -m "feat: add ResolvedPath, ReductionStep, EdgeKind types" +``` + +--- + +### Task 2: Add helper to find matching ReductionEntry candidates + +The resolver needs to iterate `inventory::iter::` filtered by name pair, check variant compatibility, and pick the most specific match. Add this as a private helper on `ReductionGraph`. + +**Files:** +- Modify: `src/rules/graph.rs` (inside the second `impl ReductionGraph` block that contains `is_variant_reducible`, after line 618) + +**Step 1: Write the failing test** + +Add to `src/unit_tests/rules/graph.rs`: + +```rust +#[test] +fn test_find_matching_entry_ksat_k3() { + let graph = ReductionGraph::new(); + let variant_k3: std::collections::BTreeMap = + [("k".to_string(), "3".to_string())].into(); + + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant_k3); + assert!(entry.is_some()); + let (source_var, _target_var, overhead) = entry.unwrap(); + // K=3 overhead has num_clauses term; K=2 does not + assert!(overhead + .output_size + .iter() + .any(|(field, _)| *field == "num_vars")); + // K=3 overhead: poly!(num_vars) + poly!(num_clauses) → two terms total + let num_vars_poly = &overhead + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert!( + num_vars_poly.terms.len() >= 2, + "K=3 overhead should have num_vars + num_clauses" + ); +} + +#[test] +fn test_find_matching_entry_ksat_k2() { + let graph = ReductionGraph::new(); + let variant_k2: std::collections::BTreeMap = + [("k".to_string(), "2".to_string())].into(); + + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant_k2); + assert!(entry.is_some()); + let (_source_var, _target_var, overhead) = entry.unwrap(); + // K=2 overhead: just poly!(num_vars) → one term + let num_vars_poly = &overhead + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert_eq!( + num_vars_poly.terms.len(), + 1, + "K=2 overhead should have only num_vars" + ); +} + +#[test] +fn test_find_matching_entry_no_match() { + let graph = ReductionGraph::new(); + let variant: std::collections::BTreeMap = + [("k".to_string(), "99".to_string())].into(); + + // k=99 is not a subtype of k=2 or k=3 + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant); + assert!(entry.is_none()); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `cargo test test_find_matching_entry -- --no-run 2>&1` +Expected: Compilation error — `find_best_entry` method not found. + +**Step 3: Implement `find_best_entry`** + +Add to `src/rules/graph.rs` in the `impl ReductionGraph` block that contains `is_variant_reducible` (after `is_variant_reducible` at ~line 618): + +```rust + /// Find the best matching `ReductionEntry` for a (source_name, target_name) pair + /// given the caller's current source variant. + /// + /// "Best" means: compatible (current variant is reducible to the entry's source variant) + /// and most specific (tightest fit among all compatible entries). + /// + /// Returns `(entry_source_variant, entry_target_variant, overhead)` or `None`. + pub fn find_best_entry( + &self, + source_name: &str, + target_name: &str, + current_variant: &std::collections::BTreeMap, + ) -> Option<( + std::collections::BTreeMap, + std::collections::BTreeMap, + ReductionOverhead, + )> { + use crate::rules::registry::ReductionEntry; + + let mut best: Option<( + std::collections::BTreeMap, + std::collections::BTreeMap, + ReductionOverhead, + )> = None; + + for entry in inventory::iter:: { + if entry.source_name != source_name || entry.target_name != target_name { + continue; + } + + let entry_source = Self::variant_to_map(&entry.source_variant()); + let entry_target = Self::variant_to_map(&entry.target_variant()); + + // Check: current_variant is reducible to entry's source variant + // (current is equal-or-more-specific on every axis) + if current_variant != &entry_source + && !self.is_variant_reducible(current_variant, &entry_source) + { + continue; + } + + // Pick the most specific: if we already have a best, prefer the one + // whose source_variant is more specific (tighter fit) + let dominated = if let Some((ref best_source, _, _)) = best { + // New entry is more specific than current best? + self.is_variant_reducible(&entry_source, best_source) + || entry_source == *current_variant + } else { + true + }; + + if dominated { + best = Some((entry_source, entry_target, entry.overhead())); + } + } + + best + } +``` + +**Step 4: Run tests to verify they pass** + +Run: `cargo test test_find_matching_entry` +Expected: All 3 tests PASS. + +**Step 5: Commit** + +```bash +git add src/rules/graph.rs src/unit_tests/rules/graph.rs +git commit -m "feat: add find_best_entry for variant-aware ReductionEntry lookup" +``` + +--- + +### Task 3: Implement `resolve_path` + +**Files:** +- Modify: `src/rules/graph.rs` (add method to `ReductionGraph`, near `find_best_entry`) + +**Step 1: Write the failing tests** + +Add to `src/unit_tests/rules/graph.rs`: + +```rust +#[test] +fn test_resolve_path_direct_same_variant() { + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + // MIS(SimpleGraph, i32) → VC(SimpleGraph, i32) — no cast needed + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + assert_eq!(resolved.num_reductions(), 1); + assert_eq!(resolved.num_casts(), 0); + assert_eq!(resolved.steps.len(), 2); + assert_eq!(resolved.steps[0].name, "MaximumIndependentSet"); + assert_eq!(resolved.steps[1].name, "MinimumVertexCover"); +} + +#[test] +fn test_resolve_path_with_natural_cast() { + use std::collections::BTreeMap; + use crate::topology::GridGraph; + let graph = ReductionGraph::new(); + + // MIS(GridGraph) → VC(SimpleGraph) — needs a natural cast MIS(GridGraph)→MIS(SimpleGraph) + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "GridGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + // Should be: MIS(GridGraph) --NaturalCast--> MIS(SimpleGraph) --Reduction--> VC(SimpleGraph) + assert_eq!(resolved.num_reductions(), 1); + assert_eq!(resolved.num_casts(), 1); + assert_eq!(resolved.steps.len(), 3); + assert_eq!(resolved.steps[0].name, "MaximumIndependentSet"); + assert_eq!( + resolved.steps[0].variant.get("graph").unwrap(), + "GridGraph" + ); + assert_eq!(resolved.steps[1].name, "MaximumIndependentSet"); + assert_eq!( + resolved.steps[1].variant.get("graph").unwrap(), + "SimpleGraph" + ); + assert_eq!(resolved.steps[2].name, "MinimumVertexCover"); + assert!(matches!(resolved.edges[0], EdgeKind::NaturalCast)); + assert!(matches!(resolved.edges[1], EdgeKind::Reduction { .. })); +} + +#[test] +fn test_resolve_path_ksat_disambiguates() { + use std::collections::BTreeMap; + use crate::rules::graph::EdgeKind; + let graph = ReductionGraph::new(); + + let name_path = graph + .find_shortest_path_by_name("KSatisfiability", "QUBO") + .unwrap(); + + // Resolve with k=3 + let source_k3 = BTreeMap::from([("k".to_string(), "3".to_string())]); + let target = BTreeMap::from([("weight".to_string(), "f64".to_string())]); + + let resolved_k3 = graph + .resolve_path(&name_path, &source_k3, &target) + .unwrap(); + assert_eq!(resolved_k3.num_reductions(), 1); + + // Extract overhead from the reduction edge + let overhead_k3 = match &resolved_k3.edges.last().unwrap() { + EdgeKind::Reduction { overhead } => overhead, + _ => panic!("last edge should be Reduction"), + }; + // K=3 overhead has 2 terms in num_vars polynomial + let num_vars_poly_k3 = &overhead_k3 + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert!(num_vars_poly_k3.terms.len() >= 2); + + // Resolve with k=2 + let source_k2 = BTreeMap::from([("k".to_string(), "2".to_string())]); + let resolved_k2 = graph + .resolve_path(&name_path, &source_k2, &target) + .unwrap(); + let overhead_k2 = match &resolved_k2.edges.last().unwrap() { + EdgeKind::Reduction { overhead } => overhead, + _ => panic!("last edge should be Reduction"), + }; + let num_vars_poly_k2 = &overhead_k2 + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert_eq!(num_vars_poly_k2.terms.len(), 1); +} + +#[test] +fn test_resolve_path_incompatible_returns_none() { + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + let name_path = graph + .find_shortest_path_by_name("KSatisfiability", "QUBO") + .unwrap(); + + // k=99 matches neither k=2 nor k=3 + let source = BTreeMap::from([("k".to_string(), "99".to_string())]); + let target = BTreeMap::from([("weight".to_string(), "f64".to_string())]); + + let resolved = graph.resolve_path(&name_path, &source, &target); + assert!(resolved.is_none()); +} +``` + +**Step 2: Run tests to verify they fail** + +Run: `cargo test test_resolve_path -- --no-run 2>&1` +Expected: Compilation error — `resolve_path` method not found. + +**Step 3: Implement `resolve_path`** + +Add to `src/rules/graph.rs` in the same `impl ReductionGraph` block, after `find_best_entry`: + +```rust + /// Resolve a name-level [`ReductionPath`] into a variant-level [`ResolvedPath`]. + /// + /// Walks the name-level path, threading variant state through each edge. + /// For each step, picks the most-specific compatible `ReductionEntry` and + /// inserts `NaturalCast` steps where the caller's variant is more specific + /// than the rule's expected source variant. + /// + /// Returns `None` if no compatible reduction entry exists for any step. + pub fn resolve_path( + &self, + path: &ReductionPath, + source_variant: &std::collections::BTreeMap, + target_variant: &std::collections::BTreeMap, + ) -> Option { + if path.type_names.len() < 2 { + return None; + } + + let mut current_variant = source_variant.clone(); + let mut steps = vec![ReductionStep { + name: path.type_names[0].to_string(), + variant: current_variant.clone(), + }]; + let mut edges = Vec::new(); + + for i in 0..path.type_names.len() - 1 { + let src_name = path.type_names[i]; + let dst_name = path.type_names[i + 1]; + + let (entry_source, entry_target, overhead) = + self.find_best_entry(src_name, dst_name, ¤t_variant)?; + + // Insert natural cast if current variant differs from entry's source + if current_variant != entry_source { + steps.push(ReductionStep { + name: src_name.to_string(), + variant: entry_source, + }); + edges.push(EdgeKind::NaturalCast); + } + + // Advance through the reduction + current_variant = entry_target; + steps.push(ReductionStep { + name: dst_name.to_string(), + variant: current_variant.clone(), + }); + edges.push(EdgeKind::Reduction { overhead }); + } + + // Trailing natural cast if final variant differs from requested target + if current_variant != *target_variant + && self.is_variant_reducible(¤t_variant, target_variant) + { + let last_name = path.type_names.last().unwrap(); + steps.push(ReductionStep { + name: last_name.to_string(), + variant: target_variant.clone(), + }); + edges.push(EdgeKind::NaturalCast); + } + + Some(ResolvedPath { steps, edges }) + } +``` + +**Step 4: Run tests to verify they pass** + +Run: `cargo test test_resolve_path` +Expected: All 4 tests PASS. + +**Step 5: Commit** + +```bash +git add src/rules/graph.rs src/unit_tests/rules/graph.rs +git commit -m "feat: add resolve_path for variant-level reduction paths" +``` + +--- + +### Task 4: Deprecate `lookup_overhead` and migrate KSat example + +**Files:** +- Modify: `src/export.rs:91-98` (add deprecation) +- Modify: `src/export.rs:100-103` (add deprecation) +- Modify: `examples/reduction_ksatisfiability_to_qubo.rs:120-121` (migrate to resolve_path) + +**Step 1: Add deprecation annotations** + +In `src/export.rs`, add `#[deprecated]` to both functions: + +```rust +#[deprecated( + since = "0.2.0", + note = "Use ReductionGraph::resolve_path() for variant-aware overhead lookup" +)] +pub fn lookup_overhead(source_name: &str, target_name: &str) -> Option { + // ... unchanged body ... +} + +#[deprecated( + since = "0.2.0", + note = "Use ReductionGraph::resolve_path() for variant-aware overhead lookup" +)] +pub fn lookup_overhead_or_empty(source_name: &str, target_name: &str) -> ReductionOverhead { + lookup_overhead(source_name, target_name).unwrap_or_default() +} +``` + +**Step 2: Migrate the KSat example** + +In `examples/reduction_ksatisfiability_to_qubo.rs`, replace the `lookup_overhead` call (line 120-121) with `resolve_path`: + +```rust + // Resolve variant-aware overhead via resolve_path + let rg = problemreductions::rules::graph::ReductionGraph::new(); + let name_path = rg + .find_shortest_path_by_name("KSatisfiability", "QUBO") + .expect("KSatisfiability -> QUBO path not found"); + let source_variant = variant_to_map(KSatisfiability::<3>::variant()) + .into_iter() + .map(|(k, v)| (k, v)) + .collect::>(); + let target_variant = variant_to_map(QUBO::::variant()) + .into_iter() + .map(|(k, v)| (k, v)) + .collect::>(); + let resolved = rg + .resolve_path(&name_path, &source_variant, &target_variant) + .expect("Failed to resolve KSatisfiability -> QUBO path"); + // Extract overhead from the reduction edge + let overhead = match resolved.edges.iter().find_map(|e| match e { + problemreductions::rules::graph::EdgeKind::Reduction { overhead } => Some(overhead), + _ => None, + }) { + Some(o) => o.clone(), + None => panic!("Resolved path has no reduction edge"), + }; +``` + +**Step 3: Verify the example still compiles and runs** + +Run: `cargo build --example reduction_ksatisfiability_to_qubo --features ilp` +Expected: Builds (deprecation warnings for other examples are OK). + +Run: `cargo run --example reduction_ksatisfiability_to_qubo --features ilp` +Expected: Runs successfully, produces JSON output. + +**Step 4: Commit** + +```bash +git add src/export.rs examples/reduction_ksatisfiability_to_qubo.rs +git commit -m "refactor: deprecate lookup_overhead, migrate KSat example to resolve_path" +``` + +--- + +### Task 5: Remove `impl_natural_reduction!` invocation from `natural.rs` + +Now that `resolve_path` inserts natural casts automatically, the explicit natural reduction registration is no longer needed for planning. Remove it and update the test. + +**Files:** +- Modify: `src/rules/natural.rs` (remove invocation) +- Modify: `src/unit_tests/rules/natural.rs` (update test to use resolve_path instead) + +**Step 1: Update the test to verify natural casts via resolve_path** + +Replace `src/unit_tests/rules/natural.rs` contents: + +```rust +use crate::models::graph::MaximumIndependentSet; +use crate::rules::graph::{EdgeKind, ReductionGraph}; +use crate::topology::{SimpleGraph, Triangular}; +use crate::traits::Problem; +use std::collections::BTreeMap; + +#[test] +fn test_natural_cast_triangular_to_simple_via_resolve() { + let graph = ReductionGraph::new(); + + // Find any path from MIS to itself (via VC round-trip) to test natural cast insertion + // Instead, directly test that resolve_path inserts a natural cast for MIS(Triangular)→VC(SimpleGraph) + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + crate::models::graph::MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "Triangular".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + // Path should be: MIS(Triangular) --NaturalCast--> MIS(SimpleGraph) --Reduction--> VC(SimpleGraph) + assert_eq!(resolved.num_casts(), 1); + assert_eq!(resolved.num_reductions(), 1); + assert!(matches!(resolved.edges[0], EdgeKind::NaturalCast)); + assert!(matches!(resolved.edges[1], EdgeKind::Reduction { .. })); + assert_eq!( + resolved.steps[0].variant.get("graph").unwrap(), + "Triangular" + ); + assert_eq!( + resolved.steps[1].variant.get("graph").unwrap(), + "SimpleGraph" + ); +} +``` + +**Step 2: Remove the `impl_natural_reduction!` invocation** + +Update `src/rules/natural.rs` to: + +```rust +//! Natural-edge reductions via graph subtype relaxation. +//! +//! Natural reductions (e.g., a problem on `Triangular` solved as `SimpleGraph`) +//! are handled automatically by [`ReductionGraph::resolve_path`], which inserts +//! `NaturalCast` steps based on the registered graph/weight subtype hierarchies. +//! +//! No explicit `ReduceTo` impls are needed for natural edges — the resolver +//! computes them from `GraphSubtypeEntry` and `WeightSubtypeEntry` registrations. + +#[cfg(test)] +#[path = "../unit_tests/rules/natural.rs"] +mod tests; +``` + +**Step 3: Run the test** + +Run: `cargo test test_natural_cast_triangular_to_simple_via_resolve` +Expected: PASS + +**Step 4: Run full test suite to check nothing broke** + +Run: `make test clippy` +Expected: PASS (some deprecation warnings for examples still using `lookup_overhead` are OK) + +**Step 5: Commit** + +```bash +git add src/rules/natural.rs src/unit_tests/rules/natural.rs +git commit -m "refactor: remove explicit natural reduction, rely on resolve_path" +``` + +--- + +### Task 6: Run full verification + +**Files:** None (verification only) + +**Step 1: Run full check** + +Run: `make check` +Expected: fmt, clippy, and all tests pass. + +**Step 2: Run doc build** + +Run: `cargo doc --no-deps -p problemreductions` +Expected: No warnings. + +**Step 3: Run examples that use lookup_overhead (should still work via deprecation)** + +Run: `cargo build --examples --features ilp` +Expected: Builds with deprecation warnings but no errors. + +**Step 4: Commit any fixups if needed, then final commit message** + +```bash +git add -A +git commit -m "chore: final cleanup for variant-aware reduction paths" +``` diff --git a/docs/plans/2026-02-14-variant-system-redesign.md b/docs/plans/2026-02-14-variant-system-redesign.md new file mode 100644 index 00000000..204dbf3f --- /dev/null +++ b/docs/plans/2026-02-14-variant-system-redesign.md @@ -0,0 +1,1160 @@ +# Robust Variant System Redesign — Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. +> **Save location:** Copy this plan to `docs/plans/2026-02-14-variant-system-redesign.md` before starting implementation. + +**Goal:** Replace the ad-hoc variant system with a unified `VariantParam` trait where graph types, weight types, and K values all self-declare their category, hierarchy position, and parent cast — eliminating hardcoded key matching, dead code, and const generic special-casing. + +**Architecture:** Three new traits (`VariantParam`, `CastToParent`, `KValue`), two `macro_rules!` macros (`impl_variant_param!`, `variant_params!`), and two new graph types (`KingsSubgraph`, `TriangularSubgraph`). The `ReductionGraph` discovers the full hierarchy from `VariantTypeEntry` inventory registrations at runtime. + +**Tech Stack:** Rust, `inventory` crate (already a dependency), `macro_rules!` (no proc macro changes needed beyond cleanup) + +--- + +## Design Decisions + +| Decision | Choice | +|----------|--------| +| Macro approach | `macro_rules!` only (no proc macro) | +| VariantParam coupling | Standalone impls (NOT supertrait of Graph/WeightElement) | +| Type parameter renaming | Skip — keys come from `VariantParam::CATEGORY` | +| Variant keys | Keep current lowercase: `"graph"`, `"weight"`, `"k"` | +| Hierarchy shape | Single-parent tree (transitivity computed by walking chain) | +| GridGraph/Triangular | Internal-only (not in public variant hierarchy) | +| New public graph types | `KingsSubgraph`, `TriangularSubgraph` (non-generic, subtype UnitDiskGraph) | +| Runtime casts | Required — `CastToParent` trait declared alongside hierarchy | +| VALUE derivation | From `stringify!($ty)` — no explicit VALUE argument needed | +| K values | Replace `const K: usize` with `K: KValue` type parameter (K2, K3, KN) | + +--- + +## Task 1: Core Variant Infrastructure + +**Files:** +- Modify: `src/variant.rs` +- Test: `src/unit_tests/variant.rs` + +### Step 1: Write failing tests for VariantParam trait and macros + +Add to `src/unit_tests/variant.rs`: + +```rust +use crate::variant::{CastToParent, VariantParam, VariantTypeEntry}; + +// Test types for the new system +#[derive(Clone, Debug)] +struct TestRoot; +#[derive(Clone, Debug)] +struct TestChild; + +impl_variant_param!(TestRoot, "test_cat"); +impl_variant_param!(TestChild, "test_cat", parent: TestRoot, cast: |_| TestRoot); + +#[test] +fn test_variant_param_root() { + assert_eq!(TestRoot::CATEGORY, "test_cat"); + assert_eq!(TestRoot::VALUE, "TestRoot"); + assert_eq!(TestRoot::PARENT_VALUE, None); +} + +#[test] +fn test_variant_param_child() { + assert_eq!(TestChild::CATEGORY, "test_cat"); + assert_eq!(TestChild::VALUE, "TestChild"); + assert_eq!(TestChild::PARENT_VALUE, Some("TestRoot")); +} + +#[test] +fn test_cast_to_parent() { + let child = TestChild; + let _parent: TestRoot = child.cast_to_parent(); +} + +#[test] +fn test_variant_type_entry_registered() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "test_cat") + .collect(); + assert!(entries.iter().any(|e| e.value == "TestRoot" && e.parent.is_none())); + assert!(entries.iter().any(|e| e.value == "TestChild" && e.parent == Some("TestRoot"))); +} + +#[derive(Clone, Debug)] +struct TestKRoot; +#[derive(Clone, Debug)] +struct TestKChild; + +impl_variant_param!(TestKRoot, "test_k", k: None); +impl_variant_param!(TestKChild, "test_k", parent: TestKRoot, cast: |_| TestKRoot, k: Some(3)); + +#[test] +fn test_kvalue_via_macro_root() { + assert_eq!(TestKRoot::CATEGORY, "test_k"); + assert_eq!(TestKRoot::VALUE, "TestKRoot"); + assert_eq!(TestKRoot::PARENT_VALUE, None); + assert_eq!(TestKRoot::K, None); +} + +#[test] +fn test_kvalue_via_macro_child() { + assert_eq!(TestKChild::CATEGORY, "test_k"); + assert_eq!(TestKChild::VALUE, "TestKChild"); + assert_eq!(TestKChild::PARENT_VALUE, Some("TestKRoot")); + assert_eq!(TestKChild::K, Some(3)); +} + +#[test] +fn test_variant_params_macro_empty() { + let v: Vec<(&str, &str)> = variant_params![]; + assert!(v.is_empty()); +} + +#[test] +fn test_variant_params_macro_single() { + fn check() -> Vec<(&'static str, &'static str)> { + variant_params![T] + } + let v = check::(); + assert_eq!(v, vec![("test_cat", "TestRoot")]); +} + +#[test] +fn test_variant_params_macro_multiple() { + fn check() -> Vec<(&'static str, &'static str)> { + variant_params![A, B] + } + let v = check::(); + assert_eq!(v, vec![("test_cat", "TestRoot"), ("test_cat", "TestChild")]); +} +``` + +### Step 2: Run tests to verify they fail + +Run: `cargo test --lib variant -- --test-output` +Expected: Compilation errors — `VariantParam`, `CastToParent`, `VariantTypeEntry`, `impl_variant_param!`, `variant_params!` don't exist yet. + +### Step 3: Implement VariantParam, CastToParent, VariantTypeEntry, and macros + +Replace `src/variant.rs` contents with: + +```rust +//! Variant system for type-level problem parameterization. +//! +//! Types declare their variant category, value, and parent via `VariantParam`. +//! The `impl_variant_param!` macro registers types with both the trait and +//! the runtime `VariantTypeEntry` inventory. The `variant_params!` macro +//! composes `Problem::variant()` bodies from type parameter names. + +/// A type that participates in the variant system. +/// +/// Declares its category (e.g., `"graph"`), value (e.g., `"SimpleGraph"`), +/// and optional parent in the subtype hierarchy. +pub trait VariantParam: 'static { + /// Category name (e.g., `"graph"`, `"weight"`, `"k"`). + const CATEGORY: &'static str; + /// Type name within the category (e.g., `"SimpleGraph"`, `"i32"`). + const VALUE: &'static str; + /// Parent type name in the subtype hierarchy, or `None` for root types. + const PARENT_VALUE: Option<&'static str>; +} + +/// Types that can convert themselves to their parent in the variant hierarchy. +pub trait CastToParent: VariantParam { + /// The parent type. + type Parent: VariantParam; + /// Convert this value to its parent type. + fn cast_to_parent(&self) -> Self::Parent; +} + +/// Runtime-discoverable variant type registration. +/// +/// Built by `impl_variant_param!` macro, collected by `inventory`. +pub struct VariantTypeEntry { + pub category: &'static str, + pub value: &'static str, + pub parent: Option<&'static str>, +} + +inventory::collect!(VariantTypeEntry); + +/// Implement `VariantParam` (and optionally `CastToParent`) for a type, +/// and register a `VariantTypeEntry` with inventory. +/// +/// # Usage +/// +/// ```rust,ignore +/// // Root type (no parent): +/// impl_variant_param!(SimpleGraph, "graph"); +/// +/// // Type with parent — cast closure required: +/// impl_variant_param!(UnitDiskGraph, "graph", parent: SimpleGraph, +/// cast: |g| SimpleGraph::new(g.num_vertices(), g.edges())); +/// ``` +#[macro_export] +macro_rules! impl_variant_param { + // Root type (no parent, no cast) + ($ty:ty, $cat:expr) => { + impl $crate::variant::VariantParam for $ty { + const CATEGORY: &'static str = $cat; + const VALUE: &'static str = stringify!($ty); + const PARENT_VALUE: Option<&'static str> = None; + } + ::inventory::submit! { + $crate::variant::VariantTypeEntry { + category: $cat, + value: stringify!($ty), + parent: None, + } + } + }; + // Type with parent + cast closure + ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr) => { + impl $crate::variant::VariantParam for $ty { + const CATEGORY: &'static str = $cat; + const VALUE: &'static str = stringify!($ty); + const PARENT_VALUE: Option<&'static str> = Some(stringify!($parent)); + } + impl $crate::variant::CastToParent for $ty { + type Parent = $parent; + fn cast_to_parent(&self) -> $parent { + let f: fn(&$ty) -> $parent = $cast; + f(self) + } + } + ::inventory::submit! { + $crate::variant::VariantTypeEntry { + category: $cat, + value: stringify!($ty), + parent: Some(stringify!($parent)), + } + } + }; + // KValue root type (no parent, with k value) + ($ty:ty, $cat:expr, k: $k:expr) => { + $crate::impl_variant_param!($ty, $cat); + impl $crate::variant::KValue for $ty { + const K: Option = $k; + } + }; + // KValue type with parent + cast + k value + ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr, k: $k:expr) => { + $crate::impl_variant_param!($ty, $cat, parent: $parent, cast: $cast); + impl $crate::variant::KValue for $ty { + const K: Option = $k; + } + }; +} + +/// Compose a `Problem::variant()` body from type parameter names. +/// +/// All variant dimensions must be types implementing `VariantParam`. +/// +/// # Usage +/// +/// ```rust,ignore +/// variant_params![] // → vec![] +/// variant_params![G, W] // → vec![(G::CATEGORY, G::VALUE), ...] +/// ``` +#[macro_export] +macro_rules! variant_params { + () => { vec![] }; + ($($T:ident),+) => { + vec![$((<$T as $crate::variant::VariantParam>::CATEGORY, + <$T as $crate::variant::VariantParam>::VALUE)),+] + }; +} + +#[cfg(test)] +#[path = "unit_tests/variant.rs"] +mod tests; +``` + +### Step 4: Run tests to verify they pass + +Run: `cargo test --lib variant` +Expected: All new tests PASS. Old tests for `short_type_name` and `const_usize_str` still pass (we haven't removed them yet). + +### Step 5: Commit + +```bash +git add src/variant.rs src/unit_tests/variant.rs +git commit -m "feat: add VariantParam trait, CastToParent, impl_variant_param!, variant_params! macros" +``` + +--- + +## Task 2: KValue Types + +**Files:** +- Modify: `src/variant.rs` +- Test: `src/unit_tests/variant.rs` + +### Step 1: Write failing tests for KValue types + +Add to `src/unit_tests/variant.rs`: + +```rust +use crate::variant::{K2, K3, KN, KValue}; + +#[test] +fn test_kvalue_k2() { + assert_eq!(K2::CATEGORY, "k"); + assert_eq!(K2::VALUE, "K2"); + assert_eq!(K2::PARENT_VALUE, Some("K3")); + assert_eq!(K2::K, Some(2)); +} + +#[test] +fn test_kvalue_k3() { + assert_eq!(K3::CATEGORY, "k"); + assert_eq!(K3::VALUE, "K3"); + assert_eq!(K3::PARENT_VALUE, Some("KN")); + assert_eq!(K3::K, Some(3)); +} + +#[test] +fn test_kvalue_kn() { + assert_eq!(KN::CATEGORY, "k"); + assert_eq!(KN::VALUE, "KN"); + assert_eq!(KN::PARENT_VALUE, None); + assert_eq!(KN::K, None); +} + +#[test] +fn test_kvalue_cast_chain() { + let k2 = K2; + let k3: K3 = k2.cast_to_parent(); + let kn: KN = k3.cast_to_parent(); + assert_eq!(KN::K, None); + let _ = kn; // use it +} + +#[test] +fn test_kvalue_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "k") + .collect(); + assert!(entries.iter().any(|e| e.value == "KN" && e.parent.is_none())); + assert!(entries.iter().any(|e| e.value == "K3" && e.parent == Some("KN"))); + assert!(entries.iter().any(|e| e.value == "K2" && e.parent == Some("K3"))); +} +``` + +### Step 2: Run tests to verify they fail + +Run: `cargo test --lib variant::tests::test_kvalue` +Expected: Compilation errors — `KValue`, `K2`, `K3`, `KN` don't exist yet. + +### Step 3: Implement KValue trait and types + +Add to `src/variant.rs` (before the `#[cfg(test)]` block): + +```rust +/// Trait for K-value types used in KSatisfiability and KColoring. +/// +/// Each type represents a specific K value (K2=2, K3=3, etc.) or +/// the generic case (KN = any K). Hierarchy: K2 < K3 < KN. +/// +/// Use `impl_variant_param!` with the `k:` argument to implement this trait: +/// ```rust,ignore +/// impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3)); +/// ``` +pub trait KValue: VariantParam + Clone + 'static { + /// The concrete K value, or `None` for the generic case (KN). + const K: Option; +} + +/// K=2 (e.g., 2-SAT, 2-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K2; + +/// K=3 (e.g., 3-SAT, 3-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K3; + +/// Generic K (any value). Used for reductions that apply to all K. +#[derive(Clone, Copy, Debug, Default)] +pub struct KN; + +impl_variant_param!(KN, "k", k: None); +impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3)); +impl_variant_param!(K2, "k", parent: K3, cast: |_| K3, k: Some(2)); +``` + +### Step 4: Run tests to verify they pass + +Run: `cargo test --lib variant` +Expected: All KValue tests PASS. + +### Step 5: Commit + +```bash +git add src/variant.rs src/unit_tests/variant.rs +git commit -m "feat: add KValue trait with K2, K3, KN types for type-level K values" +``` + +--- + +## Task 3: Register Graph Types with VariantParam + +**Files:** +- Modify: `src/topology/graph.rs` (SimpleGraph) +- Modify: `src/topology/unit_disk_graph.rs` (UnitDiskGraph) +- Modify: `src/topology/hypergraph.rs` (HyperGraph) +- Test: `src/unit_tests/variant.rs` + +### Step 1: Write failing tests + +Add to `src/unit_tests/variant.rs`: + +```rust +use crate::topology::{Graph, SimpleGraph, UnitDiskGraph}; +use crate::topology::HyperGraph; + +#[test] +fn test_simple_graph_variant_param() { + assert_eq!(SimpleGraph::CATEGORY, "graph"); + assert_eq!(SimpleGraph::VALUE, "SimpleGraph"); + assert_eq!(SimpleGraph::PARENT_VALUE, Some("HyperGraph")); +} + +#[test] +fn test_unit_disk_graph_variant_param() { + assert_eq!(UnitDiskGraph::CATEGORY, "graph"); + assert_eq!(UnitDiskGraph::VALUE, "UnitDiskGraph"); + assert_eq!(UnitDiskGraph::PARENT_VALUE, Some("SimpleGraph")); +} + +#[test] +fn test_hyper_graph_variant_param() { + assert_eq!(HyperGraph::CATEGORY, "graph"); + assert_eq!(HyperGraph::VALUE, "HyperGraph"); + assert_eq!(HyperGraph::PARENT_VALUE, None); +} + +#[test] +fn test_graph_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "graph") + .collect(); + assert!(entries.iter().any(|e| e.value == "HyperGraph" && e.parent.is_none())); + assert!(entries.iter().any(|e| e.value == "SimpleGraph" && e.parent == Some("HyperGraph"))); + assert!(entries.iter().any(|e| e.value == "UnitDiskGraph" && e.parent == Some("SimpleGraph"))); +} + +#[test] +fn test_simple_graph_cast_to_parent() { + let sg = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + let hg: HyperGraph = sg.cast_to_parent(); + assert_eq!(hg.num_vertices(), 3); + assert_eq!(hg.num_edges(), 2); +} + +#[test] +fn test_udg_cast_to_parent() { + let udg = UnitDiskGraph::new(vec![(0.0, 0.0), (0.5, 0.0), (2.0, 0.0)], 1.0); + let sg: SimpleGraph = udg.cast_to_parent(); + assert_eq!(sg.num_vertices(), 3); + // Only the first two points are within distance 1.0 + assert!(sg.has_edge(0, 1)); + assert!(!sg.has_edge(0, 2)); +} +``` + +### Step 2: Run tests to verify they fail + +Run: `cargo test --lib variant::tests::test_simple_graph` +Expected: Compilation errors — graph types don't implement `VariantParam`. + +### Step 3: Add impl_variant_param! to each graph type file + +In `src/topology/hypergraph.rs`, add at the end (before any `#[cfg(test)]`): +```rust +use crate::impl_variant_param; +impl_variant_param!(HyperGraph, "graph"); +``` + +In `src/topology/graph.rs`, add after the `SimpleGraph` impl of `Graph`: +```rust +use crate::impl_variant_param; +impl_variant_param!(SimpleGraph, "graph", parent: HyperGraph, + cast: |g| HyperGraph::from_graph_edges(g.num_vertices(), g.edges())); +``` + +In `src/topology/unit_disk_graph.rs`, add after the `UnitDiskGraph` impl of `Graph`: +```rust +use crate::impl_variant_param; +impl_variant_param!(UnitDiskGraph, "graph", parent: SimpleGraph, + cast: |g| SimpleGraph::new(g.num_vertices(), g.edges())); +``` + +Note: `HyperGraph::from_graph_edges` may need to be implemented (or use existing constructor). Check HyperGraph API and adapt the cast closure. + +### Step 4: Run tests to verify they pass + +Run: `cargo test --lib variant` +Expected: All graph variant tests PASS. + +### Step 5: Commit + +```bash +git add src/topology/graph.rs src/topology/unit_disk_graph.rs src/topology/hypergraph.rs src/unit_tests/variant.rs +git commit -m "feat: register SimpleGraph, UnitDiskGraph, HyperGraph with VariantParam" +``` + +--- + +## Task 4: Register Weight Types with VariantParam + +**Files:** +- Modify: `src/types.rs` +- Test: `src/unit_tests/variant.rs` + +### Step 1: Write failing tests + +Add to `src/unit_tests/variant.rs`: + +```rust +use crate::types::One; + +#[test] +fn test_weight_f64_variant_param() { + assert_eq!(::CATEGORY, "weight"); + assert_eq!(::VALUE, "f64"); + assert_eq!(::PARENT_VALUE, None); +} + +#[test] +fn test_weight_i32_variant_param() { + assert_eq!(::CATEGORY, "weight"); + assert_eq!(::VALUE, "i32"); + assert_eq!(::PARENT_VALUE, Some("f64")); +} + +#[test] +fn test_weight_one_variant_param() { + assert_eq!(One::CATEGORY, "weight"); + assert_eq!(One::VALUE, "One"); + assert_eq!(One::PARENT_VALUE, Some("i32")); +} + +#[test] +fn test_weight_cast_chain() { + let one = One; + let i: i32 = one.cast_to_parent(); + assert_eq!(i, 1); + let f: f64 = i.cast_to_parent(); + assert_eq!(f, 1.0); +} + +#[test] +fn test_weight_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "weight") + .collect(); + assert!(entries.iter().any(|e| e.value == "f64" && e.parent.is_none())); + assert!(entries.iter().any(|e| e.value == "i32" && e.parent == Some("f64"))); + assert!(entries.iter().any(|e| e.value == "One" && e.parent == Some("i32"))); +} +``` + +### Step 2: Implement in `src/types.rs` + +Add at end of `src/types.rs`: + +```rust +use crate::impl_variant_param; + +impl_variant_param!(f64, "weight"); +impl_variant_param!(i32, "weight", parent: f64, cast: |w| *w as f64); +impl_variant_param!(One, "weight", parent: i32, cast: |_| 1i32); +``` + +### Step 3: Run tests + +Run: `cargo test --lib variant` +Expected: All weight variant tests PASS. + +### Step 4: Commit + +```bash +git add src/types.rs src/unit_tests/variant.rs +git commit -m "feat: register One, i32, f64 with VariantParam" +``` + +--- + +## Task 5: Migrate KSatisfiability from const K to KValue + +**Files:** +- Modify: `src/models/satisfiability/ksat.rs` +- Modify: `src/unit_tests/models/satisfiability/ksat.rs` +- Modify: `src/rules/sat_ksat.rs` +- Modify: `src/rules/ksatisfiability_qubo.rs` +- Modify: `src/prelude.rs` (in `src/lib.rs`) +- Test: existing ksat tests + +### Step 1: Migrate KSatisfiability struct + +In `src/models/satisfiability/ksat.rs`, change: + +```rust +// Before: +pub struct KSatisfiability { ... } + +// After: +use crate::variant::{KValue, VariantParam}; + +pub struct KSatisfiability { + num_vars: usize, + clauses: Vec, + _phantom: std::marker::PhantomData, +} +``` + +Update all methods: replace `K` (const value) with `K::K.expect("KN cannot be instantiated")` or appropriate access. The `new()` method validates clause length using `K::K.unwrap()`. + +Update `Problem` impl: + +```rust +impl Problem for KSatisfiability { + const NAME: &'static str = "KSatisfiability"; + type Metric = bool; + + fn variant() -> Vec<(&'static str, &'static str)> { + variant_params![K] + } + + fn dims(&self) -> Vec { + vec![2; self.num_vars] + } + // ... evaluate unchanged (uses self.clauses) +} +``` + +### Step 2: Update all KSatisfiability usages + +Search-and-replace across the codebase: +- `KSatisfiability::<3>` → `KSatisfiability::` +- `KSatisfiability::<2>` → `KSatisfiability::` +- `KSatisfiability<3>` → `KSatisfiability` +- `KSatisfiability<2>` → `KSatisfiability` +- `const K: usize` in sat_ksat.rs reduction structs → `K: KValue` + +Add `use crate::variant::{K2, K3, KN};` where needed. + +### Step 3: Update reduction rules + +In `src/rules/sat_ksat.rs`: +- `ReductionSATToKSAT` → `ReductionSATToKSAT` +- `ReductionKSATToSAT` → `ReductionKSATToSAT` +- `impl_sat_to_ksat!(3)` → concrete impl for K3 + +In `src/rules/ksatisfiability_qubo.rs`: +- Update similarly + +### Step 4: Update prelude in `src/lib.rs` + +Add variant system exports to the prelude: +```rust +pub use crate::variant::{CastToParent, KValue, VariantParam, K2, K3, KN}; +``` +Note: `impl_variant_param!` and `variant_params!` are `#[macro_export]` so they're automatically available at the crate root (`problemreductions::impl_variant_param!`). + +### Step 5: Run tests + +Run: `cargo test --lib models::satisfiability::ksat && cargo test --lib rules::sat_ksat` +Expected: All existing ksat tests PASS with new type parameter syntax. + +### Step 6: Commit + +```bash +git add src/models/satisfiability/ksat.rs src/rules/sat_ksat.rs src/rules/ksatisfiability_qubo.rs src/lib.rs src/unit_tests/ +git commit -m "refactor: migrate KSatisfiability from const K to KValue type parameter" +``` + +--- + +## Task 6: Migrate KColoring from const K to KValue + +**Files:** +- Modify: `src/models/graph/kcoloring.rs` +- Modify: `src/unit_tests/models/graph/kcoloring.rs` +- Modify: `src/rules/sat_coloring.rs` +- Modify: `src/rules/coloring_qubo.rs` + +### Step 1: Migrate KColoring struct + +Same pattern as Task 5. Change `KColoring` to `KColoring`. + +```rust +impl Problem for KColoring +where G: Graph + VariantParam { + fn variant() -> Vec<(&'static str, &'static str)> { + variant_params![K, G] + } + fn dims(&self) -> Vec { + vec![K::K.expect("KN cannot be used as problem instance"); self.num_vertices()] + } +} +``` + +### Step 2: Update all KColoring usages + +- `KColoring::<3, SimpleGraph>` → `KColoring::` +- `KColoring::<2, SimpleGraph>` → `KColoring::` +- `KColoring::<4, SimpleGraph>` → add K4 type if needed, or use KN +- `KColoring::<1, SimpleGraph>` → add K1 type if needed + +Note: Tests use K=1, K=2, K=3, K=4. Add `K1` and `K4` to `src/variant.rs`: + +```rust +#[derive(Clone, Copy, Debug, Default)] +pub struct K1; +#[derive(Clone, Copy, Debug, Default)] +pub struct K4; + +impl_variant_param!(K1, "k", parent: K2, cast: |_| K2, k: Some(1)); +impl_variant_param!(K4, "k", parent: KN, cast: |_| KN, k: Some(4)); +``` + +Update hierarchy: K1 < K2 < K3 < K4 < KN. Adjust K3's parent to K4. + +### Step 3: Run tests + +Run: `cargo test --lib models::graph::kcoloring && cargo test --lib rules::sat_coloring` +Expected: All PASS. + +### Step 4: Commit + +```bash +git add src/models/graph/kcoloring.rs src/rules/sat_coloring.rs src/rules/coloring_qubo.rs src/variant.rs src/unit_tests/ +git commit -m "refactor: migrate KColoring from const K to KValue type parameter" +``` + +--- + +## Task 7: Apply variant_params! to All Problem Impls + +**Files:** +- Modify: 21 model files in `src/models/**/*.rs` +- Modify: 10 test Problem types in `src/unit_tests/` + +### Step 1: Add VariantParam bounds and variant_params! to graph+weight problems (9 files) + +For each of: `maximum_independent_set.rs`, `minimum_vertex_cover.rs`, `minimum_dominating_set.rs`, `maximum_clique.rs`, `maximum_matching.rs`, `max_cut.rs`, `maximal_is.rs`, `traveling_salesman.rs`, `spin_glass.rs`: + +```rust +// Before: +impl Problem for MaximumIndependentSet +where G: Graph, W: WeightElement { + fn variant() -> Vec<(&'static str, &'static str)> { + vec![("graph", G::NAME), ("weight", crate::variant::short_type_name::())] + } +} + +// After: +use crate::variant::VariantParam; + +impl Problem for MaximumIndependentSet +where G: Graph + VariantParam, W: WeightElement + VariantParam { + fn variant() -> Vec<(&'static str, &'static str)> { + variant_params![G, W] + } +} +``` + +### Step 2: Weight-only problems (3 files) + +For `qubo.rs`, `maximum_set_packing.rs`, `minimum_set_covering.rs`: + +```rust +where W: WeightElement + VariantParam { + fn variant() -> ... { variant_params![W] } +} +``` + +### Step 3: No-generic problems (7 files) + +For `ilp.rs`, `sat.rs`, `circuit.rs`, `factoring.rs`, `biclique_cover.rs`, `bmf.rs`, `paintshop.rs`: + +```rust +fn variant() -> ... { variant_params![] } +``` + +### Step 4: Update test Problem types + +In `src/unit_tests/traits.rs`, `src/unit_tests/solvers/brute_force.rs`, `src/unit_tests/rules/traits.rs`: + +Add `impl VariantParam` for each test-only Problem type. Since they use hardcoded variants, keep them as direct impls: + +```rust +impl VariantParam for TestSatProblem { + const CATEGORY: &'static str = "test"; + const VALUE: &'static str = "TestSatProblem"; + const PARENT_VALUE: Option<&'static str> = None; +} +``` + +Or use `variant_params![]` if the test doesn't need specific variant values. + +### Step 5: Run all tests + +Run: `make test` +Expected: All tests PASS. + +### Step 6: Commit + +```bash +git add src/models/ src/unit_tests/ +git commit -m "refactor: apply variant_params! macro to all Problem implementations" +``` + +--- + +## Task 8: New Public Graph Types (KingsSubgraph, TriangularSubgraph) + +**Files:** +- Create: `src/topology/kings_subgraph.rs` +- Create: `src/topology/triangular_subgraph.rs` +- Create: `src/unit_tests/topology/kings_subgraph.rs` +- Create: `src/unit_tests/topology/triangular_subgraph.rs` +- Modify: `src/topology/mod.rs` + +### Step 1: Implement KingsSubgraph + +Create `src/topology/kings_subgraph.rs`: + +```rust +//! KingsSubgraph: a non-generic square-grid unit disk subgraph. + +use super::graph::{Graph, SimpleGraph}; +use crate::impl_variant_param; +use serde::{Deserialize, Serialize}; + +/// Non-generic graph for square-grid unit disk subgraphs. +/// +/// Stores node positions and precomputed edges. Weights are NOT stored +/// here — they belong to the Problem that uses this graph. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KingsSubgraph { + positions: Vec<(i32, i32)>, + size: (usize, usize), + radius: f64, + edges: Vec<(usize, usize)>, +} + +impl KingsSubgraph { + /// Create from node positions and radius, computing edges. + pub fn new(positions: Vec<(i32, i32)>, size: (usize, usize), radius: f64) -> Self { + let mut edges = Vec::new(); + for i in 0..positions.len() { + for j in (i + 1)..positions.len() { + let (r1, c1) = positions[i]; + let (r2, c2) = positions[j]; + let dist = (((r1 - r2) as f64).powi(2) + ((c1 - c2) as f64).powi(2)).sqrt(); + if dist < radius { + edges.push((i, j)); + } + } + } + Self { positions, size, radius, edges } + } + + pub fn positions(&self) -> &[(i32, i32)] { &self.positions } + pub fn grid_size(&self) -> (usize, usize) { self.size } + pub fn radius(&self) -> f64 { self.radius } +} + +impl Graph for KingsSubgraph { + const NAME: &'static str = "KingsSubgraph"; + + fn num_vertices(&self) -> usize { self.positions.len() } + fn num_edges(&self) -> usize { self.edges.len() } + fn edges(&self) -> Vec<(usize, usize)> { self.edges.clone() } + + fn has_edge(&self, u: usize, v: usize) -> bool { + let (a, b) = if u < v { (u, v) } else { (v, u) }; + self.edges.iter().any(|&(x, y)| x == a && y == b) + } + + fn neighbors(&self, v: usize) -> Vec { + self.edges.iter().filter_map(|&(a, b)| { + if a == v { Some(b) } else if b == v { Some(a) } else { None } + }).collect() + } +} + +impl_variant_param!(KingsSubgraph, "graph", parent: UnitDiskGraph, + cast: |g| { + use super::unit_disk_graph::UnitDiskGraph; + let positions: Vec<(f64, f64)> = g.positions().iter() + .map(|&(r, c)| (r as f64, c as f64)) + .collect(); + UnitDiskGraph::new(positions, g.radius()) + }); +``` + +### Step 2: Implement TriangularSubgraph + +Same pattern as KingsSubgraph but with triangular coordinate system. Create `src/topology/triangular_subgraph.rs`. + +### Step 3: Update topology module exports + +In `src/topology/mod.rs`: + +```rust +mod kings_subgraph; +mod triangular_subgraph; + +pub use kings_subgraph::KingsSubgraph; +pub use triangular_subgraph::TriangularSubgraph; +``` + +### Step 4: Write tests, run, commit + +Run: `cargo test --lib topology` +Expected: PASS. + +```bash +git add src/topology/kings_subgraph.rs src/topology/triangular_subgraph.rs src/topology/mod.rs src/unit_tests/topology/ +git commit -m "feat: add KingsSubgraph and TriangularSubgraph graph types" +``` + +--- + +## Task 9: Internal-ize GridGraph/Triangular and Restructure Reductions + +**Files:** +- Modify: `src/rules/maximumindependentset_gridgraph.rs` +- Modify: `src/rules/maximumindependentset_triangular.rs` +- Modify: `src/topology/mod.rs` +- Modify: `src/graph_types.rs` + +### Step 1: Remove `#[reduction]` from GridGraph/Triangular reduction files + +The reduction code stays but is no longer registered in the variant graph. Internal unitdiskmapping still uses GridGraph/Triangular. + +### Step 2: Add new registered reductions for KingsSubgraph/TriangularSubgraph + +Restructure reduction files to output `MIS` and `MIS` instead, converting from internal GridGraph results. + +### Step 3: Make GridGraph/Triangular `pub(crate)` in topology + +Update `src/topology/mod.rs` to use `pub(crate) use` for GridGraph and Triangular. + +### Step 4: Remove GridGraph/Triangular from graph_types.rs markers + +Remove `declare_graph_subtype!` entries for GridGraph and Triangular. + +### Step 5: Run tests, commit + +Run: `make test clippy` + +```bash +git commit -m "refactor: internal-ize GridGraph/Triangular, add KingsSubgraph/TriangularSubgraph reductions" +``` + +--- + +## Task 10: Unify Hierarchy in ReductionGraph + +**Files:** +- Modify: `src/rules/graph.rs` +- Modify: `src/unit_tests/rules/graph.rs` + +### Step 1: Replace graph_hierarchy/weight_hierarchy with variant_hierarchy + +In `src/rules/graph.rs`, replace the two separate hierarchy fields with: + +```rust +variant_hierarchy: HashMap>>, +``` + +Build from `VariantTypeEntry` inventory. + +### Step 2: Generalize is_variant_reducible() + +Replace the hardcoded match on key names with generic parent-chain walk: + +```rust +fn is_subtype_in(&self, category_types: &HashMap>, a: &str, b: &str) -> bool { + if a == b { return true; } + let mut current = a; + loop { + match category_types.get(current) { + Some(Some(parent)) => { + if parent == b { return true; } + current = parent; + } + _ => return false, + } + } +} +``` + +### Step 3: Remove old hierarchy code + +Delete `is_graph_subtype`, `is_weight_subtype`, `is_const_subtype`, `graph_hierarchy`, `weight_hierarchy`. + +### Step 4: Run tests, commit + +Run: `cargo test --lib rules::graph` + +```bash +git commit -m "refactor: unify variant hierarchy in ReductionGraph using VariantTypeEntry" +``` + +--- + +## Task 11: Remove Old Hierarchy System + +**Files:** +- Modify: `src/graph_types.rs` +- Modify: `src/unit_tests/graph_types.rs` + +### Step 1: Remove from graph_types.rs + +Delete: +- `GraphSubtypeEntry`, `WeightSubtypeEntry` structs + inventory collections +- `declare_graph_subtype!`, `declare_weight_subtype!` macros + all invocations +- `GraphSubtype` trait +- `GraphMarker` trait (verify no other usages first) + +Keep: ZST marker structs (SimpleGraph, UnitDiskGraph, etc.) if used elsewhere, OR remove if superseded by topology types. + +### Step 2: Update tests + +Remove or rewrite `src/unit_tests/graph_types.rs` tests that reference deleted types. + +### Step 3: Run tests, commit + +Run: `make test clippy` + +```bash +git commit -m "refactor: remove old GraphSubtypeEntry/WeightSubtypeEntry hierarchy system" +``` + +--- + +## Task 12: Cleanup and Remove Deprecated Code + +**Files:** +- Modify: `src/variant.rs` — remove `const_usize_str`, `short_type_name` +- Modify: `problemreductions-macros/src/lib.rs` — remove const generic rewriting logic +- Modify: `src/rules/graph.rs` — remove `is_const_subtype` +- Modify: `src/unit_tests/variant.rs` — remove old tests for deleted functions + +### Step 1: Remove const_usize_str and short_type_name + +These are replaced by `KValue` types and `VariantParam::VALUE`. + +### Step 2: Remove const generic rewriting from proc macro + +In `problemreductions-macros/src/lib.rs`: +- Remove `collect_const_generic_names()` +- Remove `rewrite_const_generics()` +- Simplify `make_variant_fn_body()` — no more const generic handling needed since K is now a type param + +### Step 3: Run full test suite + +Run: `make test clippy` + +```bash +git commit -m "refactor: remove deprecated const_usize_str, short_type_name, const generic rewriting" +``` + +--- + +## Task 13: Update Examples and Downstream + +**Files:** +- Modify: `examples/reduction_*.rs` (files referencing KSatisfiability or KColoring) +- Modify: `src/lib.rs` (prelude updates) + +### Step 1: Update example files + +- `examples/reduction_ksatisfiability_to_qubo.rs` — `KSatisfiability::<3>` → `KSatisfiability::` +- `examples/reduction_kcoloring_to_qubo.rs` — `KColoring::<3, SimpleGraph>` → `KColoring::` +- `examples/reduction_kcoloring_to_ilp.rs` — similar +- `examples/reduction_satisfiability_to_ksatisfiability.rs` — `KSatisfiability<3>` → `KSatisfiability` +- `examples/reduction_satisfiability_to_kcoloring.rs` — `KColoring<3, SimpleGraph>` → `KColoring` + +### Step 2: Update integration tests + +- `tests/suites/integration.rs` — KColoring<3, ...> → KColoring +- `tests/suites/reductions.rs` — similar + +### Step 3: Run full suite + +Run: `make test` + +```bash +git commit -m "refactor: update examples and integration tests for KValue type parameters" +``` + +--- + +## Task 14: Final Verification + +### Step 1: Format and lint + +```bash +make fmt +make clippy +``` + +### Step 2: Run full test suite + +```bash +make test +``` + +### Step 3: Check coverage + +```bash +make coverage # Must remain >95% +``` + +### Step 4: Regenerate artifacts + +```bash +make rust-export +make examples +make doc +``` + +### Step 5: Verify no regressions + +```bash +make compare # Compare exported JSON +``` + +### Step 6: Final commit if needed + +```bash +git commit -m "chore: regenerate artifacts after variant system redesign" +``` + +--- + +## Key Files Summary + +| File | Change | +|------|--------| +| `src/variant.rs` | VariantParam, CastToParent, VariantTypeEntry, impl_variant_param! (4 arms: root, parent, k-root, k-parent), variant_params!, KValue, K1-K4/KN | +| `src/topology/kings_subgraph.rs` | NEW | +| `src/topology/triangular_subgraph.rs` | NEW | +| `src/topology/graph.rs` | impl_variant_param! for SimpleGraph | +| `src/topology/unit_disk_graph.rs` | impl_variant_param! for UnitDiskGraph | +| `src/topology/hypergraph.rs` | impl_variant_param! for HyperGraph | +| `src/topology/mod.rs` | Export new types, pub(crate) old ones | +| `src/types.rs` | impl_variant_param! for One, i32, f64 | +| `src/graph_types.rs` | Remove old hierarchy system | +| `src/models/satisfiability/ksat.rs` | const K → K: KValue | +| `src/models/graph/kcoloring.rs` | const K → K: KValue | +| `src/models/**/*.rs` | + VariantParam bounds, variant_params! (21 files) | +| `src/rules/graph.rs` | Unified variant_hierarchy | +| `src/rules/maximumindependentset_gridgraph.rs` | Restructure for KingsSubgraph | +| `src/rules/maximumindependentset_triangular.rs` | Restructure for TriangularSubgraph | +| `problemreductions-macros/src/lib.rs` | Remove const generic rewriting | diff --git a/docs/src/design.md b/docs/src/design.md index 990c884a..45f21a5c 100644 --- a/docs/src/design.md +++ b/docs/src/design.md @@ -69,9 +69,9 @@ Problems are parameterized by graph type and weight type: | Type | Description | |------|-------------| | `SimpleGraph` | Standard adjacency-based graph | -| `GridGraph` | Vertices on a regular grid | | `UnitDiskGraph` | Edges connect vertices within a distance threshold | -| `Triangular` | Triangular lattice graph (subtype of UnitDiskGraph) | +| `KingsSubgraph` | King's subgraph on a square grid (subtype of UnitDiskGraph) | +| `TriangularSubgraph` | Triangular lattice subgraph (subtype of UnitDiskGraph) | | `HyperGraph` | Edges connecting any number of vertices | All problem types support JSON serialization via serde: @@ -89,6 +89,17 @@ See [adding-models.md](https://github.com/CodingThrust/problem-reductions/blob/m A single problem name like `MaximumIndependentSet` can have multiple **variants** — concrete instantiations that differ in graph topology, weight type, or other parameters. The variant system tracks these distinctions in the reduction graph so that reductions between specific instantiations are represented precisely. +
+ +![Variant Hierarchy](static/variant-hierarchy.svg) + +
+
+ +![Variant Hierarchy](static/variant-hierarchy-dark.svg) + +
+ Each variant is identified by a set of key-value pairs returned by `Problem::variant()`: ```rust @@ -103,7 +114,7 @@ fn variant() -> Vec<(&'static str, &'static str)> { } ``` -Variant nodes in the reduction graph are discovered automatically from `#[reduction]` registrations — each reduction's source and target types become nodes. Natural edges between same-name variants are inferred from the graph/weight subtype partial order (e.g., `MIS/GridGraph → MIS/SimpleGraph`). In the visualization, nodes are labeled with only the non-default fields for brevity (e.g. `MaximumIndependentSet (GridGraph)` omits the default `One`). +Variant nodes in the reduction graph are discovered automatically from `#[reduction]` registrations — each reduction's source and target types become nodes. Natural edges between same-name variants are inferred from the graph/weight subtype partial order (e.g., `MIS/KingsSubgraph → MIS/SimpleGraph`). In the visualization, nodes are labeled with only the non-default fields for brevity (e.g. `MaximumIndependentSet (KingsSubgraph)` omits the default `One`). ### Graph Hierarchy @@ -115,31 +126,31 @@ HyperGraph (most general) ├── PlanarGraph ├── BipartiteGraph └── UnitDiskGraph - ├── GridGraph - └── Triangular + ├── KingsSubgraph + └── TriangularSubgraph ``` -A problem on a more specific graph type can always be treated as a problem on a more general one — a `GridGraph` *is* a `SimpleGraph`. This subtype relationship is registered at compile time: +A problem on a more specific graph type can always be treated as a problem on a more general one — a `KingsSubgraph` *is* a `SimpleGraph`. This subtype relationship is registered at compile time: ```rust -declare_graph_subtype!(GridGraph => UnitDiskGraph); +declare_graph_subtype!(KingsSubgraph => UnitDiskGraph); declare_graph_subtype!(UnitDiskGraph => SimpleGraph); // ... ``` -The runtime builds a transitive closure: `GridGraph` is a subtype of `UnitDiskGraph`, `SimpleGraph`, and `HyperGraph`. +The runtime builds a transitive closure: `KingsSubgraph` is a subtype of `UnitDiskGraph`, `SimpleGraph`, and `HyperGraph`. -**Example: natural edge for Triangular MIS.** Suppose we have a `MaximumIndependentSet` instance — an independent set problem on a triangular lattice. Because `Triangular` is a subtype of `SimpleGraph` in the graph hierarchy, the reduction graph contains a natural edge: +**Example: natural edge for TriangularSubgraph MIS.** Suppose we have a `MaximumIndependentSet` instance — an independent set problem on a triangular lattice. Because `TriangularSubgraph` is a subtype of `SimpleGraph` in the graph hierarchy, the reduction graph contains a natural edge: ``` -MIS → MIS +MIS → MIS ``` -This edge has identity overhead (the problem size is unchanged) and requires no code — the triangular lattice graph *is* a simple graph, so any MIS algorithm for general graphs applies directly. Combined with the explicit reduction `MIS → MIS` (unit disk mapping), the system can automatically chain: +This edge has identity overhead (the problem size is unchanged) and requires no code — the triangular lattice graph *is* a simple graph, so any MIS algorithm for general graphs applies directly. Combined with the explicit reduction `MIS → MIS` (unit disk mapping), the system can automatically chain: ``` -MIS → MIS → MIS - (natural edge) (explicit reduction) +MIS → MIS → MIS + (natural edge) (explicit reduction) ``` ### Weight Hierarchy @@ -159,15 +170,15 @@ declare_weight_subtype!("i32" => "f64"); ### K Parameter -`KSatisfiability` and `KColoring` use a const generic `K` mapped to a string via `const_usize_str`: +`KSatisfiability` and `KColoring` use type-level K values: | Rust type | Variant `k` | |-----------|-------------| -| `KSatisfiability<2>` | `"2"` | -| `KSatisfiability<3>` | `"3"` | -| Generic `KSatisfiability` | `"N"` | +| `KSatisfiability` | `"K2"` | +| `KSatisfiability` | `"K3"` | +| Generic `KSatisfiability` | `"KN"` | -A specific K value (e.g. `"3"`) is a subtype of the generic `"N"`, meaning any concrete K-SAT instance can be treated as a general K-SAT problem. +K values form a **flat hierarchy**: each specific K value (K1, K2, K3, K4, K5) is a direct child of the generic KN, with no chain between them. This reflects the fact that k-SAT and k-coloring problems with different k are independent problem classes — a 2-SAT instance is not a 3-SAT instance, and vice versa. ### Natural Edges @@ -203,6 +214,72 @@ MIS (SimpleGraph, i32) Both steps are identity reductions with zero overhead — no new variables or constraints are introduced. The variant system generates these edges automatically from the declared hierarchies. +### Variant-Aware Path Resolution + +The `ReductionGraph` performs path-finding at the **name level** — nodes are `"MaximumIndependentSet"`, not `"MaximumIndependentSet"`. This keeps path discovery fast (one node per problem name), but it means a `ReductionPath` like `["KSatisfiability", "QUBO"]` carries no variant information. Two issues follow: + +1. **Overhead ambiguity.** `KSatisfiability<2> → QUBO` and `KSatisfiability<3> → QUBO` have different overheads (k=3 introduces auxiliary variables via Rosenberg quadratization), but a name-level path can't distinguish them. + +2. **Natural edge execution.** The path `MIS(KingsSubgraph) → VC(SimpleGraph)` needs an implicit graph-relaxation step, but the name-level path only says `["MaximumIndependentSet", "MinimumVertexCover"]`. + +The solution is **two-phase resolution**: name-level discovery followed by variant-level resolution. + +#### `resolve_path` + +```rust +pub fn resolve_path( + &self, + path: &ReductionPath, // name-level plan + source_variant: &BTreeMap, // caller's concrete variant + target_variant: &BTreeMap, // desired target variant +) -> Option +``` + +The resolver walks the name-level path, threading variant state through each step: + +1. **Find candidates** — all `ReductionEntry` items matching `(src_name, dst_name)`. +2. **Filter compatible** — keep entries where the current variant is equal-or-more-specific than the entry's source variant on every axis. +3. **Pick most specific** — among compatible entries, choose the tightest fit. +4. **Insert natural cast** — if the current variant is more specific than the chosen entry's source, emit a `NaturalCast` edge. +5. **Advance** — update current variant to the entry's target variant, emit a `Reduction` edge with the correct overhead. + +The result is a `ResolvedPath`: + +```rust +pub struct ResolvedPath { + pub steps: Vec, // (name, variant) at each node + pub edges: Vec, // Reduction{overhead} | NaturalCast +} +``` + +#### Example: MIS on KingsSubgraph to MinimumVertexCover + +Resolving `MIS(KingsSubgraph, i32) → VC(SimpleGraph, i32)` through name-path `["MIS", "VC"]`: + +``` +steps: MIS{KingsSubgraph,i32} → MIS{SimpleGraph,i32} → VC{SimpleGraph,i32} +edges: NaturalCast Reduction{overhead} +``` + +The resolver finds that the `MIS → VC` reduction expects `SimpleGraph`, so it inserts a `NaturalCast` to relax `KingsSubgraph` to `SimpleGraph` first. + +#### Example: KSat Disambiguation + +Resolving `KSat(k=3) → QUBO` through name-path `["KSatisfiability", "QUBO"]`: + +- Candidates: `KSat<2> → QUBO` (overhead: `num_vars`) and `KSat<3> → QUBO` (overhead: `num_vars + num_clauses`). +- Filter with `k=3`: only `KSat<3>` is compatible (`3` is not a subtype of `2`). +- Result: the k=3-specific overhead is returned. + +#### Execution Model + +`ResolvedPath` is a **plan**, not an executor. Callers dispatch each step themselves: + +- `EdgeKind::Reduction` → call `ReduceTo::reduce_to()` +- `EdgeKind::NaturalCast` → call `GraphCast::cast_graph()` or equivalent weight cast + +This avoids type-erasure complexity while giving callers precise variant and overhead information at each step. + ## Rules A reduction requires two pieces: diff --git a/docs/src/introduction.md b/docs/src/introduction.md index a36138d9..e247094f 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -4,371 +4,35 @@ ## Reduction Graph -
-
-
- Graph - Set - Optimization - Satisfiability - Specialized + + + + +
+
+
+ Graph + Set + Optimization + Satisfiability + Specialized + Natural Cast
- Click a node to start path selection - + Click a node to start path selection +
-
- Click two variant nodes to find a reduction path. Double-click a node for API docs, double-click an edge for source code. Scroll to zoom, drag to pan. +
+ Click a problem node to expand/collapse its variants. + Click a variant to filter its edges. + Click two nodes to find a reduction path. + Double-click for API docs (nodes) or source code (edges). + Scroll to zoom, drag to pan.
- - - - +
For theoretical background and correctness proofs, see the [PDF manual](https://codingthrust.github.io/problem-reductions/reductions.pdf). diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index 9650acd0..7e688acb 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -18,17 +18,11 @@ "category": "optimization", "doc_path": "models/optimization/struct.ILP.html" }, - { - "name": "KColoring", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.KColoring.html" - }, { "name": "KColoring", "variant": { "graph": "SimpleGraph", - "k": "3" + "k": "K3" }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html" @@ -37,21 +31,15 @@ "name": "KColoring", "variant": { "graph": "SimpleGraph", - "k": "N" + "k": "KN" }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html" }, - { - "name": "KSatisfiability", - "variant": {}, - "category": "satisfiability", - "doc_path": "models/satisfiability/struct.KSatisfiability.html" - }, { "name": "KSatisfiability", "variant": { - "k": "2" + "k": "K2" }, "category": "satisfiability", "doc_path": "models/satisfiability/struct.KSatisfiability.html" @@ -59,7 +47,7 @@ { "name": "KSatisfiability", "variant": { - "k": "3" + "k": "K3" }, "category": "satisfiability", "doc_path": "models/satisfiability/struct.KSatisfiability.html" @@ -67,17 +55,11 @@ { "name": "KSatisfiability", "variant": { - "k": "N" + "k": "KN" }, "category": "satisfiability", "doc_path": "models/satisfiability/struct.KSatisfiability.html" }, - { - "name": "MaxCut", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaxCut.html" - }, { "name": "MaxCut", "variant": { @@ -87,12 +69,6 @@ "category": "graph", "doc_path": "models/graph/struct.MaxCut.html" }, - { - "name": "MaximumClique", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaximumClique.html" - }, { "name": "MaximumClique", "variant": { @@ -102,16 +78,10 @@ "category": "graph", "doc_path": "models/graph/struct.MaximumClique.html" }, - { - "name": "MaximumIndependentSet", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaximumIndependentSet.html" - }, { "name": "MaximumIndependentSet", "variant": { - "graph": "GridGraph", + "graph": "KingsSubgraph", "weight": "i32" }, "category": "graph", @@ -129,7 +99,7 @@ { "name": "MaximumIndependentSet", "variant": { - "graph": "Triangular", + "graph": "TriangularSubgraph", "weight": "i32" }, "category": "graph", @@ -144,12 +114,6 @@ "category": "graph", "doc_path": "models/graph/struct.MaximumIndependentSet.html" }, - { - "name": "MaximumMatching", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MaximumMatching.html" - }, { "name": "MaximumMatching", "variant": { @@ -159,12 +123,6 @@ "category": "graph", "doc_path": "models/graph/struct.MaximumMatching.html" }, - { - "name": "MaximumSetPacking", - "variant": {}, - "category": "set", - "doc_path": "models/set/struct.MaximumSetPacking.html" - }, { "name": "MaximumSetPacking", "variant": { @@ -181,12 +139,6 @@ "category": "set", "doc_path": "models/set/struct.MaximumSetPacking.html" }, - { - "name": "MinimumDominatingSet", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MinimumDominatingSet.html" - }, { "name": "MinimumDominatingSet", "variant": { @@ -196,12 +148,6 @@ "category": "graph", "doc_path": "models/graph/struct.MinimumDominatingSet.html" }, - { - "name": "MinimumSetCovering", - "variant": {}, - "category": "set", - "doc_path": "models/set/struct.MinimumSetCovering.html" - }, { "name": "MinimumSetCovering", "variant": { @@ -210,12 +156,6 @@ "category": "set", "doc_path": "models/set/struct.MinimumSetCovering.html" }, - { - "name": "MinimumVertexCover", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.MinimumVertexCover.html" - }, { "name": "MinimumVertexCover", "variant": { @@ -225,12 +165,6 @@ "category": "graph", "doc_path": "models/graph/struct.MinimumVertexCover.html" }, - { - "name": "QUBO", - "variant": {}, - "category": "optimization", - "doc_path": "models/optimization/struct.QUBO.html" - }, { "name": "QUBO", "variant": { @@ -245,12 +179,6 @@ "category": "satisfiability", "doc_path": "models/satisfiability/struct.Satisfiability.html" }, - { - "name": "SpinGlass", - "variant": {}, - "category": "optimization", - "doc_path": "models/optimization/struct.SpinGlass.html" - }, { "name": "SpinGlass", "variant": { @@ -269,12 +197,6 @@ "category": "optimization", "doc_path": "models/optimization/struct.SpinGlass.html" }, - { - "name": "TravelingSalesman", - "variant": {}, - "category": "graph", - "doc_path": "models/graph/struct.TravelingSalesman.html" - }, { "name": "TravelingSalesman", "variant": { @@ -288,7 +210,7 @@ "edges": [ { "source": 0, - "target": 35, + "target": 23, "overhead": [ { "field": "num_spins", @@ -329,7 +251,7 @@ }, { "source": 2, - "target": 31, + "target": 20, "overhead": [ { "field": "num_vars", @@ -339,8 +261,8 @@ "doc_path": "rules/ilp_qubo/index.html" }, { - "source": 4, - "target": 5, + "source": 3, + "target": 4, "overhead": [ { "field": "num_vertices", @@ -354,7 +276,7 @@ "doc_path": "" }, { - "source": 5, + "source": 4, "target": 2, "overhead": [ { @@ -369,8 +291,8 @@ "doc_path": "rules/coloring_ilp/index.html" }, { - "source": 5, - "target": 31, + "source": 4, + "target": 20, "overhead": [ { "field": "num_vars", @@ -380,8 +302,8 @@ "doc_path": "rules/coloring_qubo/index.html" }, { - "source": 7, - "target": 9, + "source": 5, + "target": 7, "overhead": [ { "field": "num_clauses", @@ -395,8 +317,8 @@ "doc_path": "" }, { - "source": 7, - "target": 31, + "source": 5, + "target": 20, "overhead": [ { "field": "num_vars", @@ -406,8 +328,23 @@ "doc_path": "rules/ksatisfiability_qubo/index.html" }, { - "source": 8, - "target": 9, + "source": 5, + "target": 21, + "overhead": [ + { + "field": "num_clauses", + "formula": "num_clauses" + }, + { + "field": "num_vars", + "formula": "num_vars" + } + ], + "doc_path": "rules/sat_ksat/index.html" + }, + { + "source": 6, + "target": 7, "overhead": [ { "field": "num_clauses", @@ -421,8 +358,8 @@ "doc_path": "" }, { - "source": 8, - "target": 31, + "source": 6, + "target": 20, "overhead": [ { "field": "num_vars", @@ -432,8 +369,8 @@ "doc_path": "rules/ksatisfiability_qubo/index.html" }, { - "source": 9, - "target": 32, + "source": 6, + "target": 21, "overhead": [ { "field": "num_clauses", @@ -447,8 +384,23 @@ "doc_path": "rules/sat_ksat/index.html" }, { - "source": 11, - "target": 35, + "source": 7, + "target": 21, + "overhead": [ + { + "field": "num_clauses", + "formula": "num_clauses" + }, + { + "field": "num_vars", + "formula": "num_vars" + } + ], + "doc_path": "rules/sat_ksat/index.html" + }, + { + "source": 8, + "target": 23, "overhead": [ { "field": "num_spins", @@ -462,7 +414,7 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 13, + "source": 9, "target": 2, "overhead": [ { @@ -477,8 +429,8 @@ "doc_path": "rules/maximumclique_ilp/index.html" }, { - "source": 15, - "target": 16, + "source": 10, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -492,8 +444,8 @@ "doc_path": "" }, { - "source": 15, - "target": 18, + "source": 10, + "target": 13, "overhead": [ { "field": "num_vertices", @@ -507,7 +459,7 @@ "doc_path": "" }, { - "source": 16, + "source": 11, "target": 2, "overhead": [ { @@ -522,8 +474,8 @@ "doc_path": "rules/maximumindependentset_ilp/index.html" }, { - "source": 16, - "target": 15, + "source": 11, + "target": 10, "overhead": [ { "field": "num_vertices", @@ -537,8 +489,8 @@ "doc_path": "rules/maximumindependentset_gridgraph/index.html" }, { - "source": 16, - "target": 17, + "source": 11, + "target": 12, "overhead": [ { "field": "num_vertices", @@ -552,8 +504,8 @@ "doc_path": "rules/maximumindependentset_triangular/index.html" }, { - "source": 16, - "target": 23, + "source": 11, + "target": 16, "overhead": [ { "field": "num_sets", @@ -567,8 +519,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 16, - "target": 29, + "source": 11, + "target": 19, "overhead": [ { "field": "num_vertices", @@ -582,8 +534,8 @@ "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" }, { - "source": 16, - "target": 31, + "source": 11, + "target": 20, "overhead": [ { "field": "num_vars", @@ -593,8 +545,8 @@ "doc_path": "rules/maximumindependentset_qubo/index.html" }, { - "source": 17, - "target": 16, + "source": 12, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -605,11 +557,11 @@ "formula": "num_edges" } ], - "doc_path": "rules/natural/index.html" + "doc_path": "" }, { - "source": 17, - "target": 18, + "source": 12, + "target": 13, "overhead": [ { "field": "num_vertices", @@ -623,8 +575,8 @@ "doc_path": "" }, { - "source": 18, - "target": 15, + "source": 13, + "target": 10, "overhead": [ { "field": "num_vertices", @@ -638,8 +590,8 @@ "doc_path": "rules/maximumindependentset_gridgraph/index.html" }, { - "source": 18, - "target": 16, + "source": 13, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -653,7 +605,7 @@ "doc_path": "" }, { - "source": 20, + "source": 14, "target": 2, "overhead": [ { @@ -668,8 +620,8 @@ "doc_path": "rules/maximummatching_ilp/index.html" }, { - "source": 20, - "target": 23, + "source": 14, + "target": 16, "overhead": [ { "field": "num_sets", @@ -683,8 +635,8 @@ "doc_path": "rules/maximummatching_maximumsetpacking/index.html" }, { - "source": 22, - "target": 31, + "source": 15, + "target": 20, "overhead": [ { "field": "num_vars", @@ -694,7 +646,7 @@ "doc_path": "rules/maximumsetpacking_qubo/index.html" }, { - "source": 23, + "source": 16, "target": 2, "overhead": [ { @@ -709,8 +661,8 @@ "doc_path": "rules/maximumsetpacking_ilp/index.html" }, { - "source": 23, - "target": 16, + "source": 16, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -724,8 +676,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 23, - "target": 22, + "source": 16, + "target": 15, "overhead": [ { "field": "num_sets", @@ -739,7 +691,7 @@ "doc_path": "" }, { - "source": 25, + "source": 17, "target": 2, "overhead": [ { @@ -754,7 +706,7 @@ "doc_path": "rules/minimumdominatingset_ilp/index.html" }, { - "source": 27, + "source": 18, "target": 2, "overhead": [ { @@ -769,7 +721,7 @@ "doc_path": "rules/minimumsetcovering_ilp/index.html" }, { - "source": 29, + "source": 19, "target": 2, "overhead": [ { @@ -784,8 +736,8 @@ "doc_path": "rules/minimumvertexcover_ilp/index.html" }, { - "source": 29, - "target": 16, + "source": 19, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -799,8 +751,8 @@ "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" }, { - "source": 29, - "target": 27, + "source": 19, + "target": 18, "overhead": [ { "field": "num_sets", @@ -814,8 +766,8 @@ "doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html" }, { - "source": 29, - "target": 31, + "source": 19, + "target": 20, "overhead": [ { "field": "num_vars", @@ -825,8 +777,8 @@ "doc_path": "rules/minimumvertexcover_qubo/index.html" }, { - "source": 31, - "target": 34, + "source": 20, + "target": 22, "overhead": [ { "field": "num_spins", @@ -836,8 +788,8 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 32, - "target": 4, + "source": 21, + "target": 3, "overhead": [ { "field": "num_vertices", @@ -851,8 +803,8 @@ "doc_path": "rules/sat_coloring/index.html" }, { - "source": 32, - "target": 8, + "source": 21, + "target": 6, "overhead": [ { "field": "num_clauses", @@ -866,8 +818,8 @@ "doc_path": "rules/sat_ksat/index.html" }, { - "source": 32, - "target": 16, + "source": 21, + "target": 11, "overhead": [ { "field": "num_vertices", @@ -881,8 +833,8 @@ "doc_path": "rules/sat_maximumindependentset/index.html" }, { - "source": 32, - "target": 25, + "source": 21, + "target": 17, "overhead": [ { "field": "num_vertices", @@ -896,8 +848,8 @@ "doc_path": "rules/sat_minimumdominatingset/index.html" }, { - "source": 34, - "target": 31, + "source": 22, + "target": 20, "overhead": [ { "field": "num_vars", @@ -907,8 +859,8 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 35, - "target": 11, + "source": 23, + "target": 8, "overhead": [ { "field": "num_vertices", @@ -922,8 +874,8 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 35, - "target": 34, + "source": 23, + "target": 22, "overhead": [ { "field": "num_spins", @@ -937,7 +889,7 @@ "doc_path": "" }, { - "source": 37, + "source": 24, "target": 2, "overhead": [ { diff --git a/docs/src/static/reduction-graph.css b/docs/src/static/reduction-graph.css new file mode 100644 index 00000000..473dcdce --- /dev/null +++ b/docs/src/static/reduction-graph.css @@ -0,0 +1,79 @@ +#cy { + width: 100%; + height: 600px; + border: 1px solid var(--sidebar-bg); + border-radius: 4px; + background: var(--bg); +} + +#cy-controls { + margin-top: 8px; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + font-family: sans-serif; + font-size: 13px; + color: var(--fg); +} + +#legend span.swatch { + display: inline-block; + width: 14px; + height: 14px; + border: 1px solid #999; + margin-right: 3px; + vertical-align: middle; + border-radius: 2px; +} + +#legend span.swatch + span.swatch { + margin-left: 10px; +} + +#cy-tooltip { + display: none; + position: absolute; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--sidebar-bg); + padding: 8px 12px; + border-radius: 4px; + font-family: sans-serif; + font-size: 13px; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + pointer-events: none; + z-index: 1000; +} + +#cy-help { + margin-top: 8px; + font-family: sans-serif; + font-size: 12px; + color: var(--fg); + opacity: 0.6; +} + +#clear-btn { + display: none; + margin-left: 8px; + padding: 3px 10px; + cursor: pointer; + font-size: 12px; +} + +#cy-search { + margin-bottom: 8px; +} + +#search-input { + padding: 4px 10px; + font-size: 13px; + font-family: sans-serif; + border: 1px solid var(--sidebar-bg); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + width: 200px; +} diff --git a/docs/src/static/reduction-graph.js b/docs/src/static/reduction-graph.js new file mode 100644 index 00000000..c12b18a2 --- /dev/null +++ b/docs/src/static/reduction-graph.js @@ -0,0 +1,585 @@ +document.addEventListener('DOMContentLoaded', function() { + // Check if the cy container exists on this page + var cyContainer = document.getElementById('cy'); + if (!cyContainer) return; + + // Register ELK layout extension if available (CDN scripts may load before cytoscape) + var elkAvailable = false; + if (typeof cytoscapeElk !== 'undefined') { + cytoscape.use(cytoscapeElk); + elkAvailable = true; + } else if (typeof cytoscape !== 'undefined' && cytoscape.use) { + // cytoscape-elk may have auto-registered if loaded after cytoscape + try { cytoscape({ headless: true, elements: [] }).layout({ name: 'elk' }); elkAvailable = true; } catch(e) {} + } + + var categoryColors = { + graph: '#c8f0c8', set: '#f0c8c8', optimization: '#f0f0a0', + satisfiability: '#c8c8f0', specialized: '#f0c8e0' + }; + var categoryBorders = { + graph: '#4a8c4a', set: '#8c4a4a', optimization: '#8c8c4a', + satisfiability: '#4a4a8c', specialized: '#8c4a6a' + }; + + function variantId(name, variant) { + var keys = Object.keys(variant).sort(); + return name + '/' + keys.map(function(k) { return k + '=' + variant[k]; }).join(','); + } + + function variantLabel(variant) { + var keys = Object.keys(variant); + if (keys.length === 0) return 'default'; + var parts = []; + keys.forEach(function(k) { + parts.push(k === 'graph' || k === 'weight' ? variant[k] : k + '=' + variant[k]); + }); + return parts.join(', '); + } + + function fullVariantLabel(variant) { + var keys = Object.keys(variant); + if (keys.length === 0) return 'no parameters'; + var parts = []; + keys.forEach(function(k) { + parts.push(k === 'graph' || k === 'weight' ? variant[k] : k + '=' + variant[k]); + }); + return parts.join(', '); + } + + fetch('reductions/reduction_graph.json') + .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) + .then(function(data) { + // Group nodes by problem name + var problems = {}; + data.nodes.forEach(function(n, idx) { + if (!problems[n.name]) { + problems[n.name] = { category: n.category, doc_path: n.doc_path, variants: [] }; + } + problems[n.name].variants.push({ index: idx, variant: n.variant, category: n.category, doc_path: n.doc_path }); + }); + + // Build edges at variant level — each directed edge is separate + var edgeMap = {}; + data.edges.forEach(function(e) { + var src = data.nodes[e.source]; + var dst = data.nodes[e.target]; + var srcId = variantId(src.name, src.variant); + var dstId = variantId(dst.name, dst.variant); + var fwd = srcId + '->' + dstId; + if (!edgeMap[fwd]) { + edgeMap[fwd] = { source: srcId, target: dstId, overhead: e.overhead || [], doc_path: e.doc_path || '' }; + } + }); + + // ── Build compound nodes ── + var elements = []; + var parentIds = {}; // name → parent node id + + Object.keys(problems).forEach(function(name) { + var info = problems[name]; + var hasMultipleVariants = info.variants.length > 1; + + if (hasMultipleVariants) { + // Create compound parent node + var parentId = 'parent_' + name; + parentIds[name] = parentId; + elements.push({ + data: { + id: parentId, + label: name, + category: info.category, + doc_path: info.doc_path, + isParent: true, + variantCount: info.variants.length + } + }); + + // Create child nodes (hidden initially — collapsed) + info.variants.forEach(function(v) { + var vid = variantId(name, v.variant); + elements.push({ + data: { + id: vid, + parent: parentId, + label: variantLabel(v.variant), + fullLabel: name + ' (' + fullVariantLabel(v.variant) + ')', + category: v.category, + doc_path: v.doc_path, + isVariant: true, + problemName: name + } + }); + }); + } else { + // Single variant — simple node (no parent) + var v = info.variants[0]; + var vid = variantId(name, v.variant); + elements.push({ + data: { + id: vid, + label: name, + fullLabel: name + ' (' + fullVariantLabel(v.variant) + ')', + category: v.category, + doc_path: v.doc_path, + isVariant: false, + problemName: name + } + }); + } + }); + + // ── Build collapsed-mode edges (name-level) — each direction is separate ── + var nameLevelEdges = {}; + data.edges.forEach(function(e) { + var srcName = data.nodes[e.source].name; + var dstName = data.nodes[e.target].name; + if (srcName === dstName) return; // skip intra-problem natural casts + var fwd = srcName + '->' + dstName; + if (!nameLevelEdges[fwd]) { + nameLevelEdges[fwd] = { count: 0, overhead: e.overhead, doc_path: e.doc_path }; + } + nameLevelEdges[fwd].count++; + }); + + // Add collapsed edges to elements + Object.keys(nameLevelEdges).forEach(function(key) { + var parts = key.split('->'); + var srcId = parentIds[parts[0]] || variantId(parts[0], problems[parts[0]].variants[0].variant); + var dstId = parentIds[parts[1]] || variantId(parts[1], problems[parts[1]].variants[0].variant); + var info = nameLevelEdges[key]; + elements.push({ + data: { + id: 'collapsed_' + key, + source: srcId, + target: dstId, + label: info.count > 1 ? '\u00d7' + info.count : '', + edgeLevel: 'collapsed', + overhead: info.overhead, + doc_path: info.doc_path + } + }); + }); + + // ── Build variant-level edges (hidden, shown when expanded) ── + Object.keys(edgeMap).forEach(function(k) { + var e = edgeMap[k]; + var srcName = e.source.split('/')[0]; + var dstName = e.target.split('/')[0]; + var isNaturalCast = srcName === dstName; + elements.push({ + data: { + id: 'variant_' + k, + source: e.source, + target: e.target, + edgeLevel: 'variant', + overhead: e.overhead, + doc_path: e.doc_path, + isNaturalCast: isNaturalCast + } + }); + }); + + var cy = cytoscape({ + container: document.getElementById('cy'), + elements: elements, + style: [ + // Use manual z-index on all elements so we have full control + // over rendering order (bypasses compound-depth conventions) + { selector: '*', style: { + 'z-index-compare': 'manual' + }}, + // Base node style (simple nodes — single variant, no parent) + { selector: 'node', style: { + 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', + 'font-size': '10px', 'font-family': 'monospace', + 'width': function(ele) { return Math.max(ele.data('label').length * 6.5 + 10, 50); }, + 'height': 24, 'shape': 'round-rectangle', + 'background-color': function(ele) { return categoryColors[ele.data('category')] || '#f0f0f0'; }, + 'border-width': 1, + 'border-color': function(ele) { return categoryBorders[ele.data('category')] || '#999'; }, + 'text-wrap': 'none', 'cursor': 'pointer', + 'z-index': 2 + }}, + // Parent (compound) node — collapsed by default + { selector: 'node[?isParent]', style: { + 'label': 'data(label)', + 'text-valign': 'center', + 'text-halign': 'center', + 'font-size': '10px', + 'font-family': 'monospace', + 'min-width': function(ele) { return Math.max(ele.data('label').length * 6.5 + 16, 60); }, + 'min-height': 28, + 'padding': '4px', + 'background-color': function(ele) { return categoryColors[ele.data('category')] || '#f0f0f0'; }, + 'border-width': 1.5, + 'border-color': function(ele) { return categoryBorders[ele.data('category')] || '#999'; }, + 'shape': 'round-rectangle', + 'compound-sizing-wrt-labels': 'include', + 'cursor': 'pointer', + 'z-index': 2 + }}, + // Parent (compound) node — expanded appearance + { selector: 'node[?isParent].expanded', style: { + 'text-valign': 'top', + 'font-size': '11px', + 'padding': '10px', + 'min-width': 0, + 'min-height': 0, + 'z-index': 5 + }}, + // Child variant nodes + { selector: 'node[?isVariant]', style: { + 'label': 'data(label)', + 'text-valign': 'center', + 'text-halign': 'center', + 'font-size': '9px', + 'font-family': 'monospace', + 'width': function(ele) { return Math.max(ele.data('label').length * 5.5 + 8, 40); }, + 'height': 18, + 'shape': 'round-rectangle', + 'background-color': function(ele) { return categoryColors[ele.data('category')] || '#f0f0f0'; }, + 'border-width': 1, + 'border-color': function(ele) { return categoryBorders[ele.data('category')] || '#999'; }, + 'cursor': 'pointer', + 'z-index': 6 + }}, + // Edge styles (z-index 1 = below nodes) + { selector: 'edge', style: { + 'width': 1.5, 'line-color': '#999', 'target-arrow-color': '#999', 'target-arrow-shape': 'triangle', + 'curve-style': 'bezier', 'arrow-scale': 0.7, 'cursor': 'pointer', + 'source-distance-from-node': 5, + 'target-distance-from-node': 5, + 'overlay-padding': 2, + 'label': 'data(label)', 'font-size': '9px', 'text-rotation': 'autorotate', + 'color': '#666', 'text-margin-y': -8, + 'z-index': 1 + }}, + // Variant-level edges (hidden programmatically after init; above expanded parent) + { selector: 'edge[edgeLevel="variant"]', style: { + 'z-index': 7 + } }, + // Natural cast edges (intra-problem) + { selector: 'edge[?isNaturalCast]', style: { + 'line-style': 'dashed', + 'line-color': '#bbb', + 'target-arrow-color': '#bbb', + 'width': 1 + }}, + // Highlighted styles + { selector: '.highlighted', style: { + 'background-color': '#ff6b6b', 'border-color': '#cc0000', 'border-width': 2, 'z-index': 20 + }}, + { selector: 'edge.highlighted', style: { + 'line-color': '#ff4444', 'target-arrow-color': '#ff4444', 'width': 3, 'z-index': 20 + }}, + { selector: '.selected-node', style: { + 'border-color': '#0066cc', 'border-width': 2, 'background-color': '#cce0ff' + }}, + { selector: '.faded', style: { 'opacity': 0.1 } }, + { selector: '.variant-selected', style: { + 'border-color': '#0066cc', + 'border-width': 2.5, + 'background-color': '#cce0ff' + }} + ], + layout: { name: 'preset' }, // delay layout until children are hidden + userZoomingEnabled: true, userPanningEnabled: true, boxSelectionEnabled: false + }); + + // Shared layout helper + function getLayoutOpts(animate) { + return elkAvailable ? { + name: 'elk', + elk: { + algorithm: 'stress', + 'stress.desiredEdgeLength': 200, + 'nodeNode.spacing': 40 + }, + nodeDimensionsIncludeLabels: true, + fit: true, + animate: animate, + animationDuration: animate ? 400 : 0, + padding: 40 + } : { + name: 'cose', + nodeDimensionsIncludeLabels: true, + fit: true, + animate: animate, + animationDuration: animate ? 300 : 0, + nodeRepulsion: function() { return 16000; }, + idealEdgeLength: function() { return 200; }, + gravity: 0.15, + numIter: 1000, + padding: 40 + }; + } + + // Run initial layout with children visible (ELK needs compound structure + // for accurate sizing/positioning), then collapse after layout completes. + cyContainer.style.opacity = '0'; + var initOpts = getLayoutOpts(false); + initOpts.stop = function() { + cy.nodes('[?isVariant]').style('display', 'none'); + cy.edges('[edgeLevel="variant"]').style('display', 'none'); + cy.fit(40); + cyContainer.style.opacity = '1'; + }; + cy.layout(initOpts).run(); + + var expandedParents = {}; // parentId → true/false + var activeVariantFilter = null; + + function toggleExpand(parentNode) { + var parentId = parentNode.id(); + var isExpanded = expandedParents[parentId]; + var children = parentNode.children(); + + if (isExpanded) { + // ── Collapse ── + children.style('display', 'none'); + parentNode.removeClass('expanded'); + expandedParents[parentId] = false; + + // Show collapsed edges connected to this parent + cy.edges('[edgeLevel="collapsed"]').forEach(function(e) { + if (e.source().id() === parentId || e.target().id() === parentId) { + e.style('display', 'element'); + } + }); + + // Hide all variant edges touching this parent's children + cy.edges('[edgeLevel="variant"]').forEach(function(e) { + var srcParent = e.source().data('parent'); + var dstParent = e.target().data('parent'); + if (srcParent === parentId || dstParent === parentId) { + e.style('display', 'none'); + } + }); + } else { + // ── Expand ── + children.style('display', 'element'); + parentNode.addClass('expanded'); + expandedParents[parentId] = true; + + // Hide collapsed edges from this parent ONLY when the other endpoint + // can be reached via variant edges. If the other endpoint is a + // collapsed compound parent, keep the collapsed edge (its children + // are hidden, so variant edges can't replace it). + cy.edges('[edgeLevel="collapsed"]').forEach(function(e) { + if (e.source().id() === parentId || e.target().id() === parentId) { + var otherId = e.source().id() === parentId ? e.target().id() : e.source().id(); + var otherNode = cy.getElementById(otherId); + var otherIsCollapsedParent = otherNode.data('isParent') && !expandedParents[otherId]; + if (!otherIsCollapsedParent) { + e.style('display', 'none'); + } + } + }); + + // Show variant edges where both endpoints are visible + cy.edges('[edgeLevel="variant"]').forEach(function(e) { + var srcParent = e.source().data('parent'); + var dstParent = e.target().data('parent'); + if (srcParent === parentId || dstParent === parentId) { + // A node is visible if it's not a variant child, + // or if its parent is expanded + var srcOk = !e.source().data('isVariant') || expandedParents[srcParent]; + var dstOk = !e.target().data('isVariant') || expandedParents[dstParent]; + if (srcOk && dstOk) { + e.style('display', 'element'); + } + } + }); + } + } + + // Tooltip for nodes + var tooltip = document.getElementById('cy-tooltip'); + cy.on('mouseover', 'node', function(evt) { + var d = evt.target.data(); + var title = d.fullLabel || d.label; + if (d.isParent) { + title += ' (' + d.variantCount + ' variants)'; + } + tooltip.innerHTML = '' + title + '
Double-click to view API docs'; + tooltip.style.display = 'block'; + }); + cy.on('mousemove', 'node', function(evt) { + var pos = evt.renderedPosition || evt.position; + var rect = document.getElementById('cy').getBoundingClientRect(); + tooltip.style.left = (rect.left + window.scrollX + pos.x + 15) + 'px'; + tooltip.style.top = (rect.top + window.scrollY + pos.y - 10) + 'px'; + }); + cy.on('mouseout', 'node', function() { tooltip.style.display = 'none'; }); + + // Edge tooltip + cy.on('mouseover', 'edge', function(evt) { + var d = evt.target.data(); + var html = '' + evt.target.source().data('label') + ' \u2192 ' + evt.target.target().data('label') + ''; + if (d.overhead && d.overhead.length > 0) { + html += '
' + d.overhead.map(function(o) { return '' + o.field + ' = ' + o.formula + ''; }).join('
'); + } + html += '
Click to highlight, double-click for source code'; + tooltip.innerHTML = html; + tooltip.style.display = 'block'; + }); + cy.on('mousemove', 'edge', function(evt) { + var pos = evt.renderedPosition || evt.position; + var rect = document.getElementById('cy').getBoundingClientRect(); + tooltip.style.left = (rect.left + window.scrollX + pos.x + 15) + 'px'; + tooltip.style.top = (rect.top + window.scrollY + pos.y - 10) + 'px'; + }); + cy.on('mouseout', 'edge', function() { tooltip.style.display = 'none'; }); + + // Double-click node → rustdoc API page + cy.on('dbltap', 'node', function(evt) { + var d = evt.target.data(); + if (d.doc_path) { + window.location.href = 'api/problemreductions/' + d.doc_path; + } + }); + // Double-click edge → GitHub source code + cy.on('dbltap', 'edge', function(evt) { + var d = evt.target.data(); + if (d.doc_path) { + var module = d.doc_path.replace('/index.html', ''); + window.open('https://github.com/CodingThrust/problem-reductions/blob/main/src/' + module + '.rs', '_blank'); + } + }); + + // Single-click path selection + var selectedNode = null; + var instructions = document.getElementById('instructions'); + var clearBtn = document.getElementById('clear-btn'); + + function clearPath() { + cy.elements().removeClass('highlighted selected-node'); + selectedNode = null; + instructions.textContent = 'Click a node to start path selection'; + clearBtn.style.display = 'none'; + } + + clearBtn.addEventListener('click', clearPath); + + cy.on('tap', 'node', function(evt) { + var node = evt.target; + + // Path selection in progress → any node completes the path + if (selectedNode) { + if (node === selectedNode) { + clearPath(); + return; + } + // For parent nodes, find path to the parent itself + var target = node; + var visibleElements = cy.elements().filter(function(ele) { + return ele.style('display') !== 'none'; + }); + var dijkstra = visibleElements.dijkstra({ root: selectedNode, directed: true }); + var path = dijkstra.pathTo(target); + cy.elements().removeClass('highlighted selected-node'); + if (path && path.length > 0) { + path.addClass('highlighted'); + instructions.textContent = 'Path: ' + path.nodes().map(function(n) { + return n.data('fullLabel') || n.data('label'); + }).join(' \u2192 '); + } else { + instructions.textContent = 'No path from ' + + (selectedNode.data('fullLabel') || selectedNode.data('label')) + + ' to ' + (target.data('fullLabel') || target.data('label')); + } + clearBtn.style.display = 'inline'; + selectedNode = null; + return; + } + + // No path selection active — Parent → expand/collapse + if (node.data('isParent')) { + toggleExpand(node); + return; + } + + // No path selection active — Variant node → variant filter + if (node.data('isVariant')) { + if (activeVariantFilter === node.id()) { + cy.elements().removeClass('faded variant-selected'); + activeVariantFilter = null; + instructions.textContent = 'Click a node to start path selection'; + return; + } + activeVariantFilter = node.id(); + cy.elements().addClass('faded'); + node.removeClass('faded').addClass('variant-selected'); + var connectedEdges = node.connectedEdges('[edgeLevel="variant"]'); + connectedEdges.removeClass('faded'); + connectedEdges.connectedNodes().removeClass('faded'); + if (node.data('parent')) { + cy.getElementById(node.data('parent')).removeClass('faded'); + } + instructions.textContent = 'Showing edges for ' + node.data('fullLabel') + ' — click again to clear'; + return; + } + + // No path selection active — Simple/any node → start path selection + selectedNode = node; + node.addClass('selected-node'); + instructions.textContent = 'Now click a target node to find path from ' + + (node.data('fullLabel') || node.data('label')); + }); + + cy.on('tap', 'edge', function(evt) { + var edge = evt.target; + var d = edge.data(); + cy.elements().removeClass('highlighted selected-node'); + edge.addClass('highlighted'); + edge.source().addClass('highlighted'); + edge.target().addClass('highlighted'); + var text = edge.source().data('label') + ' \u2192 ' + edge.target().data('label'); + if (d.overhead && d.overhead.length > 0) { + text += ' | ' + d.overhead.map(function(o) { return o.field + ' = ' + o.formula; }).join(', '); + } + instructions.textContent = text; + clearBtn.style.display = 'inline'; + selectedNode = null; + }); + + cy.on('tap', function(evt) { + if (evt.target === cy) { + clearPath(); + cy.elements().removeClass('faded variant-selected'); + activeVariantFilter = null; + } + }); + + // Search bar handler + var searchInput = document.getElementById('search-input'); + if (searchInput) { + searchInput.addEventListener('input', function() { + var query = this.value.trim().toLowerCase(); + if (query === '') { + cy.elements().removeClass('faded'); + return; + } + cy.nodes().forEach(function(node) { + var label = (node.data('label') || '').toLowerCase(); + var fullLabel = (node.data('fullLabel') || '').toLowerCase(); + if (label.includes(query) || fullLabel.includes(query)) { + node.removeClass('faded'); + } else { + node.addClass('faded'); + } + }); + cy.edges().addClass('faded'); + cy.nodes().not('.faded').connectedEdges().forEach(function(edge) { + if (!edge.source().hasClass('faded') && !edge.target().hasClass('faded')) { + edge.removeClass('faded'); + } + }); + }); + } + }) + .catch(function(err) { + document.getElementById('cy').innerHTML = '

Failed to load reduction graph: ' + err.message + '

'; + }); +}); diff --git a/docs/src/static/variant-hierarchy-dark.svg b/docs/src/static/variant-hierarchy-dark.svg new file mode 100644 index 00000000..46254999 --- /dev/null +++ b/docs/src/static/variant-hierarchy-dark.svg @@ -0,0 +1,766 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/variant-hierarchy.svg b/docs/src/static/variant-hierarchy.svg new file mode 100644 index 00000000..af4c9e24 --- /dev/null +++ b/docs/src/static/variant-hierarchy.svg @@ -0,0 +1,766 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/static/variant-hierarchy.typ b/docs/src/static/variant-hierarchy.typ new file mode 100644 index 00000000..5ae4cb7c --- /dev/null +++ b/docs/src/static/variant-hierarchy.typ @@ -0,0 +1,75 @@ +#import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge +#set page(width: auto, height: auto, margin: (top: 5pt, bottom: 5pt, left: 5pt, right: 5pt), fill: none) +#set text(font: "Helvetica Neue") + +#let variant-hierarchy(dark: false) = { + let (fg, box-color, secondary) = if dark { + (rgb("#e2e8f0"), rgb("#94a3b8"), rgb("#94a3b8")) + } else { + (rgb("#1e293b"), rgb("#64748b"), rgb("#6b7280")) + } + + let (graph-fill, weight-fill, k-fill, label-fill) = if dark { + (rgb("#1e3a5f"), rgb("#3b1f2b"), rgb("#1a3b2a"), rgb("#334155")) + } else { + (rgb("#dbeafe"), rgb("#fce7f3"), rgb("#dcfce7"), rgb("#f1f5f9")) + } + + set text(fill: fg, size: 9pt) + + // --- Graph type hierarchy --- + diagram( + node-stroke: 1.5pt + box-color, + edge-stroke: 1pt + box-color, + spacing: (10mm, 8mm), + + // Section labels + node((-0.3, -0.5), text(size: 10pt, weight: "bold")[Graph Types], stroke: none, fill: none), + node((3.2, -0.5), text(size: 10pt, weight: "bold")[Weights], stroke: none, fill: none), + node((5, -0.5), text(size: 10pt, weight: "bold")[K Values], stroke: none, fill: none), + + // Graph hierarchy (tree) + node((0, 0), [HyperGraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((0, 1), [SimpleGraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((-1, 2), [PlanarGraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((0, 2), [BipartiteGraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((1, 2), [UnitDiskGraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((0.5, 3), [KingsSubgraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((1.5, 3), [TriangularSubgraph], fill: graph-fill, corner-radius: 5pt, inset: 6pt, name: ), + + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + + // Weight hierarchy (chain: One → i32 → f64) + node((3.2, 0), [f64], fill: weight-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((3.2, 1), [i32], fill: weight-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((3.2, 2), [One], fill: weight-fill, corner-radius: 5pt, inset: 6pt, name: ), + + edge(, , "->"), + edge(, , "->"), + + // K value hierarchy (flat star) + node((5, 0), [KN], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((4.2, 1), [K1], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((4.6, 1), [K2], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((5, 1), [K3], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((5.4, 1), [K4], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + node((5.8, 1), [K5], fill: k-fill, corner-radius: 5pt, inset: 6pt, name: ), + + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + edge(, , "->"), + ) + + v(3mm) + text(size: 8pt, fill: secondary)[Arrows point from specific to general (subtype direction).] +} + +#let standalone-dark = sys.inputs.at("dark", default: "false") == "true" +#variant-hierarchy(dark: standalone-dark) diff --git a/examples/export_mapping_stages.rs b/examples/export_mapping_stages.rs index ec1f8532..a8bdead7 100644 --- a/examples/export_mapping_stages.rs +++ b/examples/export_mapping_stages.rs @@ -8,12 +8,8 @@ //! cargo run --example export_mapping_stages -- petersen triangular use problemreductions::rules::unitdiskmapping::{ - apply_crossing_gadgets, apply_simplifier_gadgets, apply_triangular_crossing_gadgets, - apply_triangular_simplifier_gadgets, apply_weighted_crossing_gadgets, - apply_weighted_simplifier_gadgets, create_copylines, mis_overhead_copyline, - mis_overhead_copyline_triangular, tape_entry_mis_overhead, triangular_tape_entry_mis_overhead, - weighted_tape_entry_mis_overhead, CopyLine, MappingGrid, TapeEntry, TriangularTapeEntry, - WeightedKsgTapeEntry, SQUARE_PADDING, SQUARE_SPACING, TRIANGULAR_PADDING, TRIANGULAR_SPACING, + create_copylines, ksg, mis_overhead_copyline, mis_overhead_copyline_triangular, triangular, + CopyLine, MappingGrid, }; use problemreductions::topology::smallgraph; use serde::Serialize; @@ -227,8 +223,8 @@ fn export_triangular( edges: &[(usize, usize)], vertex_order: &[usize], ) -> MappingExport { - let spacing = TRIANGULAR_SPACING; - let padding = TRIANGULAR_PADDING; + let spacing = triangular::SPACING; + let padding = triangular::PADDING; let copylines = create_copylines(n, edges, vertex_order); @@ -271,10 +267,10 @@ fn export_triangular( } let stage2_nodes = extract_grid_nodes(&grid); - let crossing_tape = apply_triangular_crossing_gadgets(&mut grid, ©lines, spacing, padding); + let crossing_tape = triangular::apply_crossing_gadgets(&mut grid, ©lines, spacing, padding); let stage3_nodes = extract_grid_nodes(&grid); - let simplifier_tape = apply_triangular_simplifier_gadgets(&mut grid, 10); + let simplifier_tape = triangular::apply_simplifier_gadgets(&mut grid, 10); let stage4_nodes = extract_grid_nodes(&grid); let copyline_overhead: i32 = copylines @@ -283,11 +279,11 @@ fn export_triangular( .sum(); let crossing_overhead: i32 = crossing_tape .iter() - .map(triangular_tape_entry_mis_overhead) + .map(triangular::tape_entry_mis_overhead) .sum(); let simplifier_overhead: i32 = simplifier_tape .iter() - .map(triangular_tape_entry_mis_overhead) + .map(triangular::tape_entry_mis_overhead) .sum(); let copy_lines_export = export_copylines_triangular(©lines, padding, spacing); @@ -323,8 +319,8 @@ fn export_square( edges: &[(usize, usize)], vertex_order: &[usize], ) -> MappingExport { - let spacing = SQUARE_SPACING; - let padding = SQUARE_PADDING; + let spacing = ksg::SPACING; + let padding = ksg::PADDING; let copylines = create_copylines(n, edges, vertex_order); @@ -370,18 +366,18 @@ fn export_square( } let stage2_nodes = extract_grid_nodes(&grid); - let crossing_tape = apply_crossing_gadgets(&mut grid, ©lines); + let crossing_tape = ksg::apply_crossing_gadgets(&mut grid, ©lines); let stage3_nodes = extract_grid_nodes(&grid); - let simplifier_tape = apply_simplifier_gadgets(&mut grid, 2); + let simplifier_tape = ksg::apply_simplifier_gadgets(&mut grid, 2); let stage4_nodes = extract_grid_nodes(&grid); let copyline_overhead: i32 = copylines .iter() .map(|line| mis_overhead_copyline(line, spacing, padding) as i32) .sum(); - let crossing_overhead: i32 = crossing_tape.iter().map(tape_entry_mis_overhead).sum(); - let simplifier_overhead: i32 = simplifier_tape.iter().map(tape_entry_mis_overhead).sum(); + let crossing_overhead: i32 = crossing_tape.iter().map(ksg::tape_entry_mis_overhead).sum(); + let simplifier_overhead: i32 = simplifier_tape.iter().map(ksg::tape_entry_mis_overhead).sum(); let copy_lines_export = export_copylines_square(©lines, padding, spacing); let crossing_tape_export = export_square_tape(&crossing_tape, 0); @@ -416,8 +412,8 @@ fn export_weighted( edges: &[(usize, usize)], vertex_order: &[usize], ) -> MappingExport { - let spacing = SQUARE_SPACING; - let padding = SQUARE_PADDING; + let spacing = ksg::SPACING; + let padding = ksg::PADDING; let copylines = create_copylines(n, edges, vertex_order); @@ -460,10 +456,10 @@ fn export_weighted( } let stage2_nodes = extract_grid_nodes(&grid); - let crossing_tape = apply_weighted_crossing_gadgets(&mut grid, ©lines); + let crossing_tape = ksg::apply_weighted_crossing_gadgets(&mut grid, ©lines); let stage3_nodes = extract_grid_nodes(&grid); - let simplifier_tape = apply_weighted_simplifier_gadgets(&mut grid, 2); + let simplifier_tape = ksg::apply_weighted_simplifier_gadgets(&mut grid, 2); let stage4_nodes = extract_grid_nodes(&grid); // Weighted mode: overhead = unweighted_overhead * 2 @@ -473,11 +469,11 @@ fn export_weighted( .sum(); let crossing_overhead: i32 = crossing_tape .iter() - .map(weighted_tape_entry_mis_overhead) + .map(ksg::weighted_tape_entry_mis_overhead) .sum(); let simplifier_overhead: i32 = simplifier_tape .iter() - .map(weighted_tape_entry_mis_overhead) + .map(ksg::weighted_tape_entry_mis_overhead) .sum(); let copy_lines_export = export_copylines_square(©lines, padding, spacing); @@ -592,7 +588,7 @@ fn export_copylines_square( } // IMPORTANT: Tape positions are 0-indexed. DO NOT add +1 to row/col! -fn export_triangular_tape(tape: &[TriangularTapeEntry], offset: usize) -> Vec { +fn export_triangular_tape(tape: &[triangular::WeightedTriTapeEntry], offset: usize) -> Vec { tape.iter() .enumerate() .map(|(i, e)| TapeEntryExport { @@ -601,13 +597,13 @@ fn export_triangular_tape(tape: &[TriangularTapeEntry], offset: usize) -> Vec Vec { +fn export_square_tape(tape: &[ksg::KsgTapeEntry], offset: usize) -> Vec { tape.iter() .enumerate() .map(|(i, e)| TapeEntryExport { @@ -616,14 +612,14 @@ fn export_square_tape(tape: &[TapeEntry], offset: usize) -> Vec gadget_idx: e.pattern_idx, row: e.row, // 0-indexed - DO NOT change! col: e.col, // 0-indexed - DO NOT change! - overhead: tape_entry_mis_overhead(e), + overhead: ksg::tape_entry_mis_overhead(e), }) .collect() } // IMPORTANT: Tape positions are 0-indexed. DO NOT add +1 to row/col! fn export_weighted_square_tape( - tape: &[WeightedKsgTapeEntry], + tape: &[ksg::WeightedKsgTapeEntry], offset: usize, ) -> Vec { tape.iter() @@ -634,7 +630,7 @@ fn export_weighted_square_tape( gadget_idx: e.pattern_idx, row: e.row, // 0-indexed - DO NOT change! col: e.col, // 0-indexed - DO NOT change! - overhead: weighted_tape_entry_mis_overhead(e), + overhead: ksg::weighted_tape_entry_mis_overhead(e), }) .collect() } diff --git a/examples/export_petersen_mapping.rs b/examples/export_petersen_mapping.rs index 4ffe78f8..0eeb5e39 100644 --- a/examples/export_petersen_mapping.rs +++ b/examples/export_petersen_mapping.rs @@ -27,11 +27,8 @@ //! - `docs/paper/static/petersen_square_weighted.json` - Weighted King's subgraph //! - `docs/paper/static/petersen_square_unweighted.json` - Unweighted King's subgraph //! - `docs/paper/static/petersen_triangular.json` - Weighted triangular lattice -//! -//! See docs/paper/reductions.typ for the full reduction specification. -use problemreductions::rules::unitdiskmapping::{ksg, triangular}; -use problemreductions::topology::{Graph, GridGraph}; +use problemreductions::rules::unitdiskmapping::{ksg, triangular, MappingResult}; use serde::Serialize; use std::fs; use std::path::Path; @@ -45,19 +42,47 @@ struct SourceGraph { mis: usize, } -/// Grid mapping output for visualization. +/// Grid mapping visualization data (flat format for Typst rendering). #[derive(Serialize)] -struct GridMapping { - grid_graph: GridGraph, +struct GridVisualization { + nodes: Vec, + edges: Vec<(usize, usize)>, mis_overhead: i32, padding: usize, spacing: usize, weighted: bool, } -/// Write JSON to file with pretty formatting. +#[derive(Serialize)] +struct NodeData { + row: i32, + col: i32, + weight: i32, +} + +impl GridVisualization { + fn from_result(result: &MappingResult, weighted: bool) -> Self { + let nodes: Vec = result + .positions + .iter() + .zip(result.node_weights.iter()) + .map(|(&(row, col), &weight)| NodeData { row, col, weight }) + .collect(); + let edges = result.edges(); + GridVisualization { + nodes, + edges, + mis_overhead: result.mis_overhead, + padding: result.padding, + spacing: result.spacing, + weighted, + } + } +} + +/// Write JSON to file in compact format. fn write_json(data: &T, path: &Path) { - let json = serde_json::to_string_pretty(data).expect("Failed to serialize to JSON"); + let json = serde_json::to_string(data).expect("Failed to serialize to JSON"); if let Some(parent) = path.parent() { fs::create_dir_all(parent).expect("Failed to create output directory"); } @@ -65,17 +90,6 @@ fn write_json(data: &T, path: &Path) { println!(" Wrote: {}", path.display()); } -/// Create a GridMapping from a MappingResult by using the actual grid_graph. -fn make_grid_mapping(result: &ksg::MappingResult, weighted: bool) -> GridMapping { - GridMapping { - grid_graph: result.grid_graph.clone(), - mis_overhead: result.mis_overhead, - padding: result.padding, - spacing: result.spacing, - weighted, - } -} - fn main() { println!("\n=== Independent Set to Grid Graph IS (Unit Disk Mapping) ===\n"); @@ -126,78 +140,69 @@ fn main() { // Map to weighted King's subgraph (square lattice) println!("1. King's Subgraph (Weighted)"); let square_weighted_result = ksg::map_weighted(num_vertices, &petersen_edges); - let square_weighted = make_grid_mapping(&square_weighted_result, true); + let square_weighted_viz = GridVisualization::from_result(&square_weighted_result, true); println!( - " Grid size: {}×{}", - square_weighted.grid_graph.size().0, - square_weighted.grid_graph.size().1 + " Vertices: {}, Edges: {}", + square_weighted_viz.nodes.len(), + square_weighted_viz.edges.len() ); println!( - " Vertices: {}, Edges: {}", - square_weighted.grid_graph.num_vertices(), - square_weighted.grid_graph.num_edges() + " MIS overhead Δ: {}", + square_weighted_result.mis_overhead ); - println!(" MIS overhead Δ: {}", square_weighted.mis_overhead); println!( " MIS(grid) = MIS(source) + Δ = {} + {} = {}", petersen_mis, - square_weighted.mis_overhead, - petersen_mis as i32 + square_weighted.mis_overhead + square_weighted_result.mis_overhead, + petersen_mis as i32 + square_weighted_result.mis_overhead ); write_json( - &square_weighted, + &square_weighted_viz, Path::new("docs/paper/static/petersen_square_weighted.json"), ); // Map to unweighted King's subgraph (square lattice) println!("\n2. King's Subgraph (Unweighted)"); let square_unweighted_result = ksg::map_unweighted(num_vertices, &petersen_edges); - let square_unweighted = make_grid_mapping(&square_unweighted_result, false); + let square_unweighted_viz = GridVisualization::from_result(&square_unweighted_result, false); println!( - " Grid size: {}×{}", - square_unweighted.grid_graph.size().0, - square_unweighted.grid_graph.size().1 + " Vertices: {}, Edges: {}", + square_unweighted_viz.nodes.len(), + square_unweighted_viz.edges.len() ); println!( - " Vertices: {}, Edges: {}", - square_unweighted.grid_graph.num_vertices(), - square_unweighted.grid_graph.num_edges() + " MIS overhead Δ: {}", + square_unweighted_result.mis_overhead ); - println!(" MIS overhead Δ: {}", square_unweighted.mis_overhead); println!( " MIS(grid) = MIS(source) + Δ = {} + {} = {}", petersen_mis, - square_unweighted.mis_overhead, - petersen_mis as i32 + square_unweighted.mis_overhead + square_unweighted_result.mis_overhead, + petersen_mis as i32 + square_unweighted_result.mis_overhead ); write_json( - &square_unweighted, + &square_unweighted_viz, Path::new("docs/paper/static/petersen_square_unweighted.json"), ); // Map to weighted triangular lattice println!("\n3. Triangular Lattice (Weighted)"); let triangular_result = triangular::map_weighted(num_vertices, &petersen_edges); - let triangular_weighted = make_grid_mapping(&triangular_result, true); - println!( - " Grid size: {}×{}", - triangular_weighted.grid_graph.size().0, - triangular_weighted.grid_graph.size().1 - ); + let triangular_viz = GridVisualization::from_result(&triangular_result, true); println!( " Vertices: {}, Edges: {}", - triangular_weighted.grid_graph.num_vertices(), - triangular_weighted.grid_graph.num_edges() + triangular_viz.nodes.len(), + triangular_viz.edges.len() ); - println!(" MIS overhead Δ: {}", triangular_weighted.mis_overhead); + println!(" MIS overhead Δ: {}", triangular_result.mis_overhead); println!( " MIS(grid) = MIS(source) + Δ = {} + {} = {}", petersen_mis, - triangular_weighted.mis_overhead, - petersen_mis as i32 + triangular_weighted.mis_overhead + triangular_result.mis_overhead, + petersen_mis as i32 + triangular_result.mis_overhead ); write_json( - &triangular_weighted, + &triangular_viz, Path::new("docs/paper/static/petersen_triangular.json"), ); @@ -206,21 +211,21 @@ fn main() { println!(); println!( "King's subgraph (weighted): {} vertices, MIS = {} (overhead Δ = {})", - square_weighted.grid_graph.num_vertices(), - petersen_mis as i32 + square_weighted.mis_overhead, - square_weighted.mis_overhead + square_weighted_viz.nodes.len(), + petersen_mis as i32 + square_weighted_result.mis_overhead, + square_weighted_result.mis_overhead ); println!( "King's subgraph (unweighted): {} vertices, MIS = {} (overhead Δ = {})", - square_unweighted.grid_graph.num_vertices(), - petersen_mis as i32 + square_unweighted.mis_overhead, - square_unweighted.mis_overhead + square_unweighted_viz.nodes.len(), + petersen_mis as i32 + square_unweighted_result.mis_overhead, + square_unweighted_result.mis_overhead ); println!( "Triangular lattice (weighted): {} vertices, MIS = {} (overhead Δ = {})", - triangular_weighted.grid_graph.num_vertices(), - petersen_mis as i32 + triangular_weighted.mis_overhead, - triangular_weighted.mis_overhead + triangular_viz.nodes.len(), + petersen_mis as i32 + triangular_result.mis_overhead, + triangular_result.mis_overhead ); println!("\n✓ Unit disk mapping demonstrated successfully"); diff --git a/examples/reduction_circuitsat_to_spinglass.rs b/examples/reduction_circuitsat_to_spinglass.rs index f7c7f940..9e9675d4 100644 --- a/examples/reduction_circuitsat_to_spinglass.rs +++ b/examples/reduction_circuitsat_to_spinglass.rs @@ -131,13 +131,15 @@ pub fn run() { println!("\nReduction verified successfully"); // 5. Export JSON - let overhead = lookup_overhead("CircuitSAT", "SpinGlass") + let source_variant = variant_to_map(CircuitSAT::variant()); + let target_variant = variant_to_map(SpinGlass::::variant()); + let overhead = lookup_overhead("CircuitSAT", &source_variant, "SpinGlass", &target_variant) .expect("CircuitSAT -> SpinGlass overhead not found"); let data = ReductionData { source: ProblemSide { problem: CircuitSAT::NAME.to_string(), - variant: variant_to_map(CircuitSAT::variant()), + variant: source_variant, instance: serde_json::json!({ "num_gates": circuit_sat.circuit().num_assignments(), "num_variables": circuit_sat.num_variables(), @@ -145,7 +147,7 @@ pub fn run() { }, target: ProblemSide { problem: SpinGlass::::NAME.to_string(), - variant: variant_to_map(SpinGlass::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_spins": sg.num_variables(), }), diff --git a/examples/reduction_factoring_to_circuitsat.rs b/examples/reduction_factoring_to_circuitsat.rs index e281c55a..22a3651c 100644 --- a/examples/reduction_factoring_to_circuitsat.rs +++ b/examples/reduction_factoring_to_circuitsat.rs @@ -190,13 +190,15 @@ pub fn run() { println!("\nReduction verified successfully: 35 = 5 * 7"); // 6. Export JSON - let overhead = lookup_overhead("Factoring", "CircuitSAT") + let source_variant = variant_to_map(Factoring::variant()); + let target_variant = variant_to_map(CircuitSAT::variant()); + let overhead = lookup_overhead("Factoring", &source_variant, "CircuitSAT", &target_variant) .expect("Factoring -> CircuitSAT overhead not found"); let data = ReductionData { source: ProblemSide { problem: Factoring::NAME.to_string(), - variant: variant_to_map(Factoring::variant()), + variant: source_variant, instance: serde_json::json!({ "number": factoring.target(), "num_bits_first": factoring.m(), @@ -205,7 +207,7 @@ pub fn run() { }, target: ProblemSide { problem: CircuitSAT::NAME.to_string(), - variant: variant_to_map(CircuitSAT::variant()), + variant: target_variant, instance: serde_json::json!({ "num_variables": circuit_sat.num_variables(), "num_gates": circuit_sat.circuit().num_assignments(), diff --git a/examples/reduction_factoring_to_ilp.rs b/examples/reduction_factoring_to_ilp.rs index 83723d08..960a0265 100644 --- a/examples/reduction_factoring_to_ilp.rs +++ b/examples/reduction_factoring_to_ilp.rs @@ -70,13 +70,15 @@ pub fn run() { target_config: ilp_solution, }]; - let overhead = - lookup_overhead("Factoring", "ILP").expect("Factoring -> ILP overhead not found"); + let source_variant = variant_to_map(Factoring::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("Factoring", &source_variant, "ILP", &target_variant) + .expect("Factoring -> ILP overhead not found"); let data = ReductionData { source: ProblemSide { problem: Factoring::NAME.to_string(), - variant: variant_to_map(Factoring::variant()), + variant: source_variant, instance: serde_json::json!({ "number": problem.target(), "num_bits_first": problem.m(), @@ -85,7 +87,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_ilp_to_qubo.rs b/examples/reduction_ilp_to_qubo.rs index cf4c38dd..2c1018cf 100644 --- a/examples/reduction_ilp_to_qubo.rs +++ b/examples/reduction_ilp_to_qubo.rs @@ -135,19 +135,22 @@ pub fn run() { println!("\nVerification passed: all solutions are feasible and optimal"); // Export JSON - let overhead = lookup_overhead("ILP", "QUBO").expect("ILP -> QUBO overhead not found"); + let source_variant = variant_to_map(ILP::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead("ILP", &source_variant, "QUBO", &target_variant) + .expect("ILP -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, }), }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_kcoloring_to_ilp.rs b/examples/reduction_kcoloring_to_ilp.rs index 01130477..b299410f 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>::new(num_vertices, edges.clone()); + let coloring = KColoring::::new(num_vertices, edges.clone()); // 2. Reduce to ILP let reduction = ReduceTo::::reduce_to(&coloring); @@ -70,13 +70,15 @@ pub fn run() { target_config: ilp_solution, }); - let overhead = - lookup_overhead("KColoring", "ILP").expect("KColoring -> ILP overhead not found"); + let source_variant = variant_to_map(KColoring::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("KColoring", &source_variant, "ILP", &target_variant) + .expect("KColoring -> ILP overhead not found"); let data = ReductionData { source: ProblemSide { - problem: KColoring::<3, SimpleGraph>::NAME.to_string(), - variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()), + problem: KColoring::::NAME.to_string(), + variant: source_variant, instance: serde_json::json!({ "num_vertices": coloring.num_vertices(), "num_edges": coloring.num_edges(), @@ -85,7 +87,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_kcoloring_to_qubo.rs b/examples/reduction_kcoloring_to_qubo.rs index b9a7b7ca..fa783984 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>::new(num_vertices, edges.clone()); + let kc = KColoring::::new(num_vertices, edges.clone()); // Reduce to QUBO let reduction = ReduceTo::::reduce_to(&kc); @@ -88,13 +88,15 @@ pub fn run() { ); // Export JSON - let overhead = - lookup_overhead("KColoring", "QUBO").expect("KColoring -> QUBO overhead not found"); + let source_variant = variant_to_map(KColoring::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead("KColoring", &source_variant, "QUBO", &target_variant) + .expect("KColoring -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { - problem: KColoring::<3, SimpleGraph>::NAME.to_string(), - variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()), + problem: KColoring::::NAME.to_string(), + variant: source_variant, instance: serde_json::json!({ "num_vertices": kc.num_vertices(), "num_edges": kc.num_edges(), @@ -103,7 +105,7 @@ pub fn run() { }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_ksatisfiability_to_qubo.rs b/examples/reduction_ksatisfiability_to_qubo.rs index ed7d1608..d62686be 100644 --- a/examples/reduction_ksatisfiability_to_qubo.rs +++ b/examples/reduction_ksatisfiability_to_qubo.rs @@ -62,13 +62,13 @@ pub fn run() { "x3 OR NOT x4 OR NOT x5".to_string(), ]; - let ksat = KSatisfiability::<3>::new(5, clauses); + let ksat = KSatisfiability::::new(5, clauses); // Reduce to QUBO let reduction = ReduceTo::::reduce_to(&ksat); let qubo = reduction.target_problem(); - println!("Source: KSatisfiability<3> with 5 variables, 7 clauses"); + println!("Source: KSatisfiability with 5 variables, 7 clauses"); for (i, c) in clause_strings.iter().enumerate() { println!(" C{}: {}", i + 1, c); } @@ -117,13 +117,15 @@ pub fn run() { println!("\nVerification passed: all solutions maximize satisfied clauses"); // Export JSON - let overhead = lookup_overhead("KSatisfiability", "QUBO") + let source_variant = variant_to_map(KSatisfiability::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead("KSatisfiability", &source_variant, "QUBO", &target_variant) .expect("KSatisfiability -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { - problem: KSatisfiability::<3>::NAME.to_string(), - variant: variant_to_map(KSatisfiability::<3>::variant()), + problem: KSatisfiability::::NAME.to_string(), + variant: source_variant, instance: serde_json::json!({ "num_vars": ksat.num_vars(), "num_clauses": ksat.clauses().len(), @@ -132,7 +134,7 @@ pub fn run() { }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_maxcut_to_spinglass.rs b/examples/reduction_maxcut_to_spinglass.rs index da27eb0b..1e8aa43b 100644 --- a/examples/reduction_maxcut_to_spinglass.rs +++ b/examples/reduction_maxcut_to_spinglass.rs @@ -60,13 +60,15 @@ pub fn run() { // Export JSON let edges: Vec<(usize, usize, i32)> = maxcut.edges(); - let overhead = - lookup_overhead("MaxCut", "SpinGlass").expect("MaxCut -> SpinGlass overhead not found"); + let source_variant = variant_to_map(MaxCut::::variant()); + let target_variant = variant_to_map(SpinGlass::::variant()); + let overhead = lookup_overhead("MaxCut", &source_variant, "SpinGlass", &target_variant) + .expect("MaxCut -> SpinGlass overhead not found"); let data = ReductionData { source: ProblemSide { problem: MaxCut::::NAME.to_string(), - variant: variant_to_map(MaxCut::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": maxcut.num_vertices(), "num_edges": maxcut.num_edges(), @@ -75,7 +77,7 @@ pub fn run() { }, target: ProblemSide { problem: SpinGlass::::NAME.to_string(), - variant: variant_to_map(SpinGlass::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_spins": sg.num_variables(), }), diff --git a/examples/reduction_maximumclique_to_ilp.rs b/examples/reduction_maximumclique_to_ilp.rs index 4c43971a..a431f21a 100644 --- a/examples/reduction_maximumclique_to_ilp.rs +++ b/examples/reduction_maximumclique_to_ilp.rs @@ -71,12 +71,15 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MaximumClique", "ILP"); + let source_variant = variant_to_map(MaximumClique::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("MaximumClique", &source_variant, "ILP", &target_variant) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MaximumClique::::NAME.to_string(), - variant: variant_to_map(MaximumClique::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": clique.num_vertices(), "num_edges": clique.num_edges(), @@ -85,7 +88,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_maximumindependentset_to_ilp.rs b/examples/reduction_maximumindependentset_to_ilp.rs index 5bf753fe..a7e96ba6 100644 --- a/examples/reduction_maximumindependentset_to_ilp.rs +++ b/examples/reduction_maximumindependentset_to_ilp.rs @@ -70,12 +70,20 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MaximumIndependentSet", "ILP"); + let source_variant = variant_to_map(MaximumIndependentSet::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "ILP", + &target_variant, + ) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": is.num_vertices(), "num_edges": is.num_edges(), @@ -84,7 +92,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs index 048c5c8b..e7c64f19 100644 --- a/examples/reduction_maximumindependentset_to_maximumsetpacking.rs +++ b/examples/reduction_maximumindependentset_to_maximumsetpacking.rs @@ -93,13 +93,20 @@ pub fn run() { ); // Export JSON - let overhead = lookup_overhead("MaximumIndependentSet", "MaximumSetPacking") - .expect("MaximumIndependentSet -> MaximumSetPacking overhead not found"); + let source_variant = variant_to_map(MaximumIndependentSet::::variant()); + let target_variant = variant_to_map(MaximumSetPacking::::variant()); + let overhead = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "MaximumSetPacking", + &target_variant, + ) + .expect("MaximumIndependentSet -> MaximumSetPacking overhead not found"); let data = ReductionData { source: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": source.num_vertices(), "num_edges": source.num_edges(), @@ -108,7 +115,7 @@ pub fn run() { }, target: ProblemSide { problem: MaximumSetPacking::::NAME.to_string(), - variant: variant_to_map(MaximumSetPacking::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_sets": target.num_sets(), "sets": target.sets(), diff --git a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs index eeb6f1b3..22adca73 100644 --- a/examples/reduction_maximumindependentset_to_minimumvertexcover.rs +++ b/examples/reduction_maximumindependentset_to_minimumvertexcover.rs @@ -60,14 +60,21 @@ pub fn run() { println!("Reduction verified successfully"); // 6. Export JSON - let overhead = lookup_overhead("MaximumIndependentSet", "MinimumVertexCover") - .expect("MaximumIndependentSet -> MinimumVertexCover overhead not found"); + let source_variant = variant_to_map(MaximumIndependentSet::::variant()); + let target_variant = variant_to_map(MinimumVertexCover::::variant()); + let overhead = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "MinimumVertexCover", + &target_variant, + ) + .expect("MaximumIndependentSet -> MinimumVertexCover overhead not found"); let vc_edges = vc.edges(); let data = ReductionData { source: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": is.num_vertices(), "num_edges": is.num_edges(), @@ -76,7 +83,7 @@ pub fn run() { }, target: ProblemSide { problem: MinimumVertexCover::::NAME.to_string(), - variant: variant_to_map(MinimumVertexCover::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vertices": vc.num_vertices(), "num_edges": vc.num_edges(), diff --git a/examples/reduction_maximumindependentset_to_qubo.rs b/examples/reduction_maximumindependentset_to_qubo.rs index e645836a..22e2c934 100644 --- a/examples/reduction_maximumindependentset_to_qubo.rs +++ b/examples/reduction_maximumindependentset_to_qubo.rs @@ -80,13 +80,20 @@ pub fn run() { println!("\nVerification passed: all solutions are valid"); // Export JSON - let overhead = lookup_overhead("MaximumIndependentSet", "QUBO") - .expect("MaximumIndependentSet -> QUBO overhead not found"); + let source_variant = variant_to_map(MaximumIndependentSet::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "QUBO", + &target_variant, + ) + .expect("MaximumIndependentSet -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": is.num_vertices(), "num_edges": is.num_edges(), @@ -95,7 +102,7 @@ pub fn run() { }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_maximummatching_to_ilp.rs b/examples/reduction_maximummatching_to_ilp.rs index 0fdff47b..380b43ca 100644 --- a/examples/reduction_maximummatching_to_ilp.rs +++ b/examples/reduction_maximummatching_to_ilp.rs @@ -70,12 +70,15 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MaximumMatching", "ILP"); + let source_variant = variant_to_map(MaximumMatching::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("MaximumMatching", &source_variant, "ILP", &target_variant) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MaximumMatching::::NAME.to_string(), - variant: variant_to_map(MaximumMatching::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": matching.num_vertices(), "num_edges": matching.num_edges(), @@ -84,7 +87,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_maximummatching_to_maximumsetpacking.rs b/examples/reduction_maximummatching_to_maximumsetpacking.rs index e36217c7..3fd56c5f 100644 --- a/examples/reduction_maximummatching_to_maximumsetpacking.rs +++ b/examples/reduction_maximummatching_to_maximumsetpacking.rs @@ -70,13 +70,20 @@ pub fn run() { } // Export JSON - let overhead = lookup_overhead("MaximumMatching", "MaximumSetPacking") - .expect("MaximumMatching -> MaximumSetPacking overhead not found"); + let source_variant = variant_to_map(MaximumMatching::::variant()); + let target_variant = variant_to_map(MaximumSetPacking::::variant()); + let overhead = lookup_overhead( + "MaximumMatching", + &source_variant, + "MaximumSetPacking", + &target_variant, + ) + .expect("MaximumMatching -> MaximumSetPacking overhead not found"); let data = ReductionData { source: ProblemSide { problem: MaximumMatching::::NAME.to_string(), - variant: variant_to_map(MaximumMatching::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": source.num_vertices(), "num_edges": source.num_edges(), @@ -85,7 +92,7 @@ pub fn run() { }, target: ProblemSide { problem: MaximumSetPacking::::NAME.to_string(), - variant: variant_to_map(MaximumSetPacking::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_sets": target.num_sets(), "sets": target.sets(), diff --git a/examples/reduction_maximumsetpacking_to_ilp.rs b/examples/reduction_maximumsetpacking_to_ilp.rs index 9c2147dc..1de5320a 100644 --- a/examples/reduction_maximumsetpacking_to_ilp.rs +++ b/examples/reduction_maximumsetpacking_to_ilp.rs @@ -76,12 +76,15 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MaximumSetPacking", "ILP"); + let source_variant = variant_to_map(MaximumSetPacking::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("MaximumSetPacking", &source_variant, "ILP", &target_variant) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MaximumSetPacking::::NAME.to_string(), - variant: variant_to_map(MaximumSetPacking::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_sets": sp.num_sets(), "sets": sp.sets(), @@ -89,7 +92,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_maximumsetpacking_to_qubo.rs b/examples/reduction_maximumsetpacking_to_qubo.rs index 22564e93..4ceacc8a 100644 --- a/examples/reduction_maximumsetpacking_to_qubo.rs +++ b/examples/reduction_maximumsetpacking_to_qubo.rs @@ -97,13 +97,20 @@ pub fn run() { println!("\nVerification passed: all solutions are valid set packings"); // Export JSON - let overhead = lookup_overhead("MaximumSetPacking", "QUBO") - .expect("MaximumSetPacking -> QUBO overhead not found"); + let source_variant = variant_to_map(MaximumSetPacking::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead( + "MaximumSetPacking", + &source_variant, + "QUBO", + &target_variant, + ) + .expect("MaximumSetPacking -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { problem: MaximumSetPacking::::NAME.to_string(), - variant: variant_to_map(MaximumSetPacking::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_sets": sp.num_sets(), "sets": sp.sets(), @@ -111,7 +118,7 @@ pub fn run() { }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_minimumdominatingset_to_ilp.rs b/examples/reduction_minimumdominatingset_to_ilp.rs index 270d3af8..6a90c688 100644 --- a/examples/reduction_minimumdominatingset_to_ilp.rs +++ b/examples/reduction_minimumdominatingset_to_ilp.rs @@ -72,12 +72,20 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MinimumDominatingSet", "ILP"); + let source_variant = variant_to_map(MinimumDominatingSet::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead( + "MinimumDominatingSet", + &source_variant, + "ILP", + &target_variant, + ) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MinimumDominatingSet::::NAME.to_string(), - variant: variant_to_map(MinimumDominatingSet::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": ds.num_vertices(), "num_edges": ds.num_edges(), @@ -86,7 +94,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_minimumsetcovering_to_ilp.rs b/examples/reduction_minimumsetcovering_to_ilp.rs index b9c8c3f5..e921dd71 100644 --- a/examples/reduction_minimumsetcovering_to_ilp.rs +++ b/examples/reduction_minimumsetcovering_to_ilp.rs @@ -79,12 +79,20 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MinimumSetCovering", "ILP"); + let source_variant = variant_to_map(MinimumSetCovering::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead( + "MinimumSetCovering", + &source_variant, + "ILP", + &target_variant, + ) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MinimumSetCovering::::NAME.to_string(), - variant: variant_to_map(MinimumSetCovering::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_sets": sc.num_sets(), "sets": sc.sets(), @@ -93,7 +101,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_minimumvertexcover_to_ilp.rs b/examples/reduction_minimumvertexcover_to_ilp.rs index f2807bfc..70eff45c 100644 --- a/examples/reduction_minimumvertexcover_to_ilp.rs +++ b/examples/reduction_minimumvertexcover_to_ilp.rs @@ -72,12 +72,20 @@ pub fn run() { }); } - let overhead = lookup_overhead_or_empty("MinimumVertexCover", "ILP"); + let source_variant = variant_to_map(MinimumVertexCover::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead( + "MinimumVertexCover", + &source_variant, + "ILP", + &target_variant, + ) + .unwrap_or_default(); let data = ReductionData { source: ProblemSide { problem: MinimumVertexCover::::NAME.to_string(), - variant: variant_to_map(MinimumVertexCover::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": vc.num_vertices(), "num_edges": vc.num_edges(), @@ -86,7 +94,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs index b0869be6..f48d72fb 100644 --- a/examples/reduction_minimumvertexcover_to_maximumindependentset.rs +++ b/examples/reduction_minimumvertexcover_to_maximumindependentset.rs @@ -68,13 +68,20 @@ pub fn run() { // Export JSON let vc_edges = vc.edges(); let is_edges = is.edges(); - let overhead = lookup_overhead("MinimumVertexCover", "MaximumIndependentSet") - .expect("MinimumVertexCover -> MaximumIndependentSet overhead not found"); + let source_variant = variant_to_map(MinimumVertexCover::::variant()); + let target_variant = variant_to_map(MaximumIndependentSet::::variant()); + let overhead = lookup_overhead( + "MinimumVertexCover", + &source_variant, + "MaximumIndependentSet", + &target_variant, + ) + .expect("MinimumVertexCover -> MaximumIndependentSet overhead not found"); let data = ReductionData { source: ProblemSide { problem: MinimumVertexCover::::NAME.to_string(), - variant: variant_to_map(MinimumVertexCover::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": vc.num_vertices(), "num_edges": vc.num_edges(), @@ -83,7 +90,7 @@ pub fn run() { }, target: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vertices": is.num_vertices(), "num_edges": is.num_edges(), diff --git a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs index b8dff814..b7ca1b25 100644 --- a/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs +++ b/examples/reduction_minimumvertexcover_to_minimumsetcovering.rs @@ -95,13 +95,20 @@ pub fn run() { ); // Export JSON - let overhead = lookup_overhead("MinimumVertexCover", "MinimumSetCovering") - .expect("MinimumVertexCover -> MinimumSetCovering overhead not found"); + let source_variant = variant_to_map(MinimumVertexCover::::variant()); + let target_variant = variant_to_map(MinimumSetCovering::::variant()); + let overhead = lookup_overhead( + "MinimumVertexCover", + &source_variant, + "MinimumSetCovering", + &target_variant, + ) + .expect("MinimumVertexCover -> MinimumSetCovering overhead not found"); let data = ReductionData { source: ProblemSide { problem: MinimumVertexCover::::NAME.to_string(), - variant: variant_to_map(MinimumVertexCover::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": source.num_vertices(), "num_edges": source.num_edges(), @@ -110,7 +117,7 @@ pub fn run() { }, target: ProblemSide { problem: MinimumSetCovering::::NAME.to_string(), - variant: variant_to_map(MinimumSetCovering::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_sets": target.num_sets(), "sets": target.sets(), diff --git a/examples/reduction_minimumvertexcover_to_qubo.rs b/examples/reduction_minimumvertexcover_to_qubo.rs index 855193ca..09ada7c1 100644 --- a/examples/reduction_minimumvertexcover_to_qubo.rs +++ b/examples/reduction_minimumvertexcover_to_qubo.rs @@ -89,13 +89,20 @@ pub fn run() { println!("\nVerification passed: all solutions are valid with size 6"); // Export JSON - let overhead = lookup_overhead("MinimumVertexCover", "QUBO") - .expect("MinimumVertexCover -> QUBO overhead not found"); + let source_variant = variant_to_map(MinimumVertexCover::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead( + "MinimumVertexCover", + &source_variant, + "QUBO", + &target_variant, + ) + .expect("MinimumVertexCover -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { problem: MinimumVertexCover::::NAME.to_string(), - variant: variant_to_map(MinimumVertexCover::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": vc.num_vertices(), "num_edges": vc.num_edges(), @@ -104,7 +111,7 @@ pub fn run() { }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_qubo_to_spinglass.rs b/examples/reduction_qubo_to_spinglass.rs index e122e3d0..99af9517 100644 --- a/examples/reduction_qubo_to_spinglass.rs +++ b/examples/reduction_qubo_to_spinglass.rs @@ -67,13 +67,15 @@ pub fn run() { } // Export JSON - let overhead = - lookup_overhead("QUBO", "SpinGlass").expect("QUBO -> SpinGlass overhead not found"); + let source_variant = variant_to_map(QUBO::::variant()); + let target_variant = variant_to_map(SpinGlass::::variant()); + let overhead = lookup_overhead("QUBO", &source_variant, "SpinGlass", &target_variant) + .expect("QUBO -> SpinGlass overhead not found"); let data = ReductionData { source: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": matrix, @@ -81,7 +83,7 @@ pub fn run() { }, target: ProblemSide { problem: SpinGlass::::NAME.to_string(), - variant: variant_to_map(SpinGlass::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_spins": sg.num_variables(), }), diff --git a/examples/reduction_satisfiability_to_kcoloring.rs b/examples/reduction_satisfiability_to_kcoloring.rs index e19e9215..1f7f85ab 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> - let reduction = ReduceTo::>::reduce_to(&sat); + // SAT reduces to KColoring + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); println!("\n=== Problem Transformation ==="); @@ -115,21 +115,28 @@ pub fn run() { println!("\nReduction verified successfully"); // 5. Export JSON - let overhead = lookup_overhead("Satisfiability", "KColoring") - .expect("Satisfiability -> KColoring overhead not found"); + let source_variant = variant_to_map(Satisfiability::variant()); + let target_variant = variant_to_map(KColoring::::variant()); + let overhead = lookup_overhead( + "Satisfiability", + &source_variant, + "KColoring", + &target_variant, + ) + .expect("Satisfiability -> KColoring overhead not found"); let data = ReductionData { source: ProblemSide { problem: Satisfiability::NAME.to_string(), - variant: variant_to_map(Satisfiability::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": sat.num_vars(), "num_clauses": sat.num_clauses(), }), }, target: ProblemSide { - problem: KColoring::<3, SimpleGraph>::NAME.to_string(), - variant: variant_to_map(KColoring::<3, SimpleGraph>::variant()), + problem: KColoring::::NAME.to_string(), + variant: target_variant, instance: serde_json::json!({ "num_vertices": coloring.num_vertices(), "num_edges": coloring.num_edges(), diff --git a/examples/reduction_satisfiability_to_ksatisfiability.rs b/examples/reduction_satisfiability_to_ksatisfiability.rs index 6d0d219c..817ce307 100644 --- a/examples/reduction_satisfiability_to_ksatisfiability.rs +++ b/examples/reduction_satisfiability_to_ksatisfiability.rs @@ -52,7 +52,7 @@ pub fn run() { println!(" Clause sizes: 1, 2, 3, 3, 4, 5 (demonstrates padding and splitting)"); // 2. Reduce to 3-SAT (K=3) - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); println!("\n=== Problem Transformation ==="); @@ -123,21 +123,28 @@ pub fn run() { println!("\nReduction verified successfully"); // 5. Export JSON - let overhead = lookup_overhead("Satisfiability", "KSatisfiability") - .expect("Satisfiability -> KSatisfiability overhead not found"); + let source_variant = variant_to_map(Satisfiability::variant()); + let target_variant = variant_to_map(KSatisfiability::::variant()); + let overhead = lookup_overhead( + "Satisfiability", + &source_variant, + "KSatisfiability", + &target_variant, + ) + .expect("Satisfiability -> KSatisfiability overhead not found"); let data = ReductionData { source: ProblemSide { problem: Satisfiability::NAME.to_string(), - variant: variant_to_map(Satisfiability::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": sat.num_vars(), "num_clauses": sat.num_clauses(), }), }, target: ProblemSide { - problem: KSatisfiability::<3>::NAME.to_string(), - variant: variant_to_map(KSatisfiability::<3>::variant()), + problem: KSatisfiability::::NAME.to_string(), + variant: target_variant, instance: serde_json::json!({ "num_vars": ksat.num_vars(), "num_clauses": ksat.num_clauses(), diff --git a/examples/reduction_satisfiability_to_maximumindependentset.rs b/examples/reduction_satisfiability_to_maximumindependentset.rs index 0cb3f130..74ed27d0 100644 --- a/examples/reduction_satisfiability_to_maximumindependentset.rs +++ b/examples/reduction_satisfiability_to_maximumindependentset.rs @@ -104,13 +104,20 @@ pub fn run() { println!("\nReduction verified successfully"); // 5. Export JSON - let overhead = lookup_overhead("Satisfiability", "MaximumIndependentSet") - .expect("Satisfiability -> MaximumIndependentSet overhead not found"); + let source_variant = variant_to_map(Satisfiability::variant()); + let target_variant = variant_to_map(MaximumIndependentSet::::variant()); + let overhead = lookup_overhead( + "Satisfiability", + &source_variant, + "MaximumIndependentSet", + &target_variant, + ) + .expect("Satisfiability -> MaximumIndependentSet overhead not found"); let data = ReductionData { source: ProblemSide { problem: Satisfiability::NAME.to_string(), - variant: variant_to_map(Satisfiability::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": sat.num_vars(), "num_clauses": sat.num_clauses(), @@ -118,7 +125,7 @@ pub fn run() { }, target: ProblemSide { problem: MaximumIndependentSet::::NAME.to_string(), - variant: variant_to_map(MaximumIndependentSet::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vertices": is.num_vertices(), "num_edges": is.num_edges(), diff --git a/examples/reduction_satisfiability_to_minimumdominatingset.rs b/examples/reduction_satisfiability_to_minimumdominatingset.rs index f02f8c34..6f87f59a 100644 --- a/examples/reduction_satisfiability_to_minimumdominatingset.rs +++ b/examples/reduction_satisfiability_to_minimumdominatingset.rs @@ -118,13 +118,20 @@ pub fn run() { } // 6. Export JSON - let overhead = lookup_overhead("Satisfiability", "MinimumDominatingSet") - .expect("Satisfiability -> MinimumDominatingSet overhead not found"); + let source_variant = variant_to_map(Satisfiability::variant()); + let target_variant = variant_to_map(MinimumDominatingSet::::variant()); + let overhead = lookup_overhead( + "Satisfiability", + &source_variant, + "MinimumDominatingSet", + &target_variant, + ) + .expect("Satisfiability -> MinimumDominatingSet overhead not found"); let data = ReductionData { source: ProblemSide { problem: Satisfiability::NAME.to_string(), - variant: variant_to_map(Satisfiability::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vars": sat.num_vars(), "num_clauses": sat.num_clauses(), @@ -132,7 +139,7 @@ pub fn run() { }, target: ProblemSide { problem: MinimumDominatingSet::::NAME.to_string(), - variant: variant_to_map(MinimumDominatingSet::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vertices": ds.num_vertices(), "num_edges": ds.num_edges(), diff --git a/examples/reduction_spinglass_to_maxcut.rs b/examples/reduction_spinglass_to_maxcut.rs index 1b6364eb..b1294e4b 100644 --- a/examples/reduction_spinglass_to_maxcut.rs +++ b/examples/reduction_spinglass_to_maxcut.rs @@ -64,20 +64,22 @@ pub fn run() { println!("\nReduction verified successfully"); // Export JSON - let overhead = - lookup_overhead("SpinGlass", "MaxCut").expect("SpinGlass -> MaxCut overhead not found"); + let source_variant = variant_to_map(SpinGlass::::variant()); + let target_variant = variant_to_map(MaxCut::::variant()); + let overhead = lookup_overhead("SpinGlass", &source_variant, "MaxCut", &target_variant) + .expect("SpinGlass -> MaxCut overhead not found"); let data = ReductionData { source: ProblemSide { problem: SpinGlass::::NAME.to_string(), - variant: variant_to_map(SpinGlass::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_spins": sg.num_variables(), }), }, target: ProblemSide { problem: MaxCut::::NAME.to_string(), - variant: variant_to_map(MaxCut::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vertices": maxcut.num_vertices(), "num_edges": maxcut.num_edges(), diff --git a/examples/reduction_spinglass_to_qubo.rs b/examples/reduction_spinglass_to_qubo.rs index 86af4852..311414dd 100644 --- a/examples/reduction_spinglass_to_qubo.rs +++ b/examples/reduction_spinglass_to_qubo.rs @@ -62,20 +62,22 @@ pub fn run() { } // Export JSON - let overhead = - lookup_overhead("SpinGlass", "QUBO").expect("SpinGlass -> QUBO overhead not found"); + let source_variant = variant_to_map(SpinGlass::::variant()); + let target_variant = variant_to_map(QUBO::::variant()); + let overhead = lookup_overhead("SpinGlass", &source_variant, "QUBO", &target_variant) + .expect("SpinGlass -> QUBO overhead not found"); let data = ReductionData { source: ProblemSide { problem: SpinGlass::::NAME.to_string(), - variant: variant_to_map(SpinGlass::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_spins": sg.num_variables(), }), }, target: ProblemSide { problem: QUBO::::NAME.to_string(), - variant: variant_to_map(QUBO::::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": qubo.num_vars(), "matrix": qubo.matrix(), diff --git a/examples/reduction_travelingsalesman_to_ilp.rs b/examples/reduction_travelingsalesman_to_ilp.rs index bbba74d3..127a16de 100644 --- a/examples/reduction_travelingsalesman_to_ilp.rs +++ b/examples/reduction_travelingsalesman_to_ilp.rs @@ -77,13 +77,16 @@ pub fn run() { target_config: ilp_solution, }]; - let overhead = lookup_overhead_or_empty("TravelingSalesman", "ILP"); + let source_variant = variant_to_map(TravelingSalesman::::variant()); + let target_variant = variant_to_map(ILP::variant()); + let overhead = lookup_overhead("TravelingSalesman", &source_variant, "ILP", &target_variant) + .unwrap_or_default(); let edges: Vec<(usize, usize)> = problem.edges().iter().map(|&(u, v, _)| (u, v)).collect(); let data = ReductionData { source: ProblemSide { problem: TravelingSalesman::::NAME.to_string(), - variant: variant_to_map(TravelingSalesman::::variant()), + variant: source_variant, instance: serde_json::json!({ "num_vertices": problem.num_vertices(), "num_edges": problem.num_edges(), @@ -92,7 +95,7 @@ pub fn run() { }, target: ProblemSide { problem: ILP::NAME.to_string(), - variant: variant_to_map(ILP::variant()), + variant: target_variant, instance: serde_json::json!({ "num_vars": ilp.num_vars, "num_constraints": ilp.constraints.len(), diff --git a/problemreductions-macros/src/lib.rs b/problemreductions-macros/src/lib.rs index f4b5b5af..6340b7c1 100644 --- a/problemreductions-macros/src/lib.rs +++ b/problemreductions-macros/src/lib.rs @@ -12,8 +12,7 @@ use syn::{parse_macro_input, GenericArgument, ItemImpl, Path, PathArguments, Typ /// Attribute macro for automatic reduction registration. /// /// Parses a `ReduceTo` impl block and generates the corresponding `inventory::submit!` -/// call. Variant fields are derived from `Problem::variant()`. Const generics like `K` -/// are substituted with `usize::MAX` (maps to `"N"` via `const_usize_str`). +/// call. Variant fields are derived from `Problem::variant()`. /// /// **Type generics are not supported** — all `ReduceTo` impls must use concrete types. /// If you need a reduction for a generic problem, write separate impls for each concrete @@ -80,22 +79,6 @@ fn extract_type_name(ty: &Type) -> Option { } } -/// Collect const generic parameter names from impl generics. -/// e.g., `impl` → {"K"} -fn collect_const_generic_names(generics: &syn::Generics) -> HashSet { - 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", "W"} fn collect_type_generic_names(generics: &syn::Generics) -> HashSet { @@ -135,43 +118,11 @@ fn type_uses_type_generics(ty: &Type, type_generics: &HashSet) -> bool { } } -/// Rewrite a type by substituting const generic names with `{usize::MAX}`. -/// -/// e.g., `KColoring` with const_generics={"K"} -/// → `KColoring<{usize::MAX}, SimpleGraph>` -fn rewrite_const_generics(ty: &Type, const_generics: &HashSet) -> 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. /// -/// Calls `Problem::variant()` with const generic sentinels. +/// Calls `Problem::variant()` on the concrete type. /// Errors if the type uses any type generics — all `ReduceTo` impls must be concrete. -fn make_variant_fn_body( - ty: &Type, - const_generics: &HashSet, - type_generics: &HashSet, -) -> syn::Result { +fn make_variant_fn_body(ty: &Type, type_generics: &HashSet) -> syn::Result { if type_uses_type_generics(ty, type_generics) { let used: Vec<_> = type_generics.iter().cloned().collect(); return Err(syn::Error::new_spanned( @@ -183,8 +134,7 @@ fn make_variant_fn_body( ), )); } - let rewritten = rewrite_const_generics(ty, const_generics); - Ok(quote! { <#rewritten as crate::traits::Problem>::variant() }) + Ok(quote! { <#ty as crate::traits::Problem>::variant() }) } /// Generate the reduction entry code @@ -212,12 +162,11 @@ fn generate_reduction_entry( .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)?; - let target_variant_body = make_variant_fn_body(&target_type, &const_generics, &type_generics)?; + let source_variant_body = make_variant_fn_body(source_type, &type_generics)?; + let target_variant_body = make_variant_fn_body(&target_type, &type_generics)?; // Generate overhead or use default let overhead = attrs.overhead.clone().unwrap_or_else(|| { diff --git a/src/export.rs b/src/export.rs index fa023219..32639855 100644 --- a/src/export.rs +++ b/src/export.rs @@ -8,9 +8,10 @@ //! The schema mirrors the internal types: `ReductionOverhead` for polynomials, //! `Problem::variant()` for problem variants, and `Problem::NAME` for problem names. -use crate::rules::registry::{ReductionEntry, ReductionOverhead}; +use crate::rules::registry::ReductionOverhead; +use crate::rules::ReductionGraph; use serde::Serialize; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fs; use std::path::Path; @@ -83,23 +84,23 @@ pub fn overhead_to_json(overhead: &ReductionOverhead) -> Vec { .collect() } -/// Look up `ReductionOverhead` from inventory by source and target problem names. +/// Look up `ReductionOverhead` for a direct reduction using `ReductionGraph::find_best_entry`. /// -/// Searches all registered `ReductionEntry` items for a matching source/target pair. -/// Returns `None` if no matching reduction is registered (e.g., ILP reductions -/// that don't use the `#[reduction]` macro). -pub fn lookup_overhead(source_name: &str, target_name: &str) -> Option { - for entry in inventory::iter:: { - if entry.source_name == source_name && entry.target_name == target_name { - return Some(entry.overhead()); - } - } - None -} - -/// Look up overhead, returning an empty overhead if not registered. -pub fn lookup_overhead_or_empty(source_name: &str, target_name: &str) -> ReductionOverhead { - lookup_overhead(source_name, target_name).unwrap_or_default() +/// Finds the best matching registered reduction entry for the given source/target +/// names and source variant. Returns `None` if no compatible direct reduction exists. +pub fn lookup_overhead( + source_name: &str, + source_variant: &HashMap, + target_name: &str, + _target_variant: &HashMap, +) -> Option { + let graph = ReductionGraph::new(); + let src_bt: BTreeMap = source_variant + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + let matched = graph.find_best_entry(source_name, target_name, &src_bt)?; + Some(matched.overhead) } /// Convert `Problem::variant()` output to a `HashMap`. diff --git a/src/graph_types.rs b/src/graph_types.rs index 6334403e..26ecf9c8 100644 --- a/src/graph_types.rs +++ b/src/graph_types.rs @@ -1,125 +1,61 @@ //! Graph type markers for parametric problem modeling. - -use inventory; - -/// Marker trait for graph types. -pub trait GraphMarker: 'static + Clone + Send + Sync {} - -/// Compile-time subtype relationship between graph types. -pub trait GraphSubtype: GraphMarker {} - -// Reflexive: every type is a subtype of itself -impl GraphSubtype for G {} +//! +//! ZST marker structs for graph types used as type parameters in problem definitions. +//! The subtype hierarchy is managed via `VariantTypeEntry` registrations (see `src/variant.rs`). /// Simple (arbitrary) graph - the most general graph type. #[derive(Debug, Clone, Copy, Default)] pub struct SimpleGraph; -impl GraphMarker for SimpleGraph {} - /// Planar graph - can be drawn on a plane without edge crossings. #[derive(Debug, Clone, Copy, Default)] pub struct PlanarGraph; -impl GraphMarker for PlanarGraph {} +impl crate::variant::VariantParam for PlanarGraph { + const CATEGORY: &'static str = "graph"; + const VALUE: &'static str = "PlanarGraph"; + const PARENT_VALUE: Option<&'static str> = Some("SimpleGraph"); +} +inventory::submit! { + crate::variant::VariantTypeEntry { + category: "graph", + value: "PlanarGraph", + parent: Some("SimpleGraph"), + } +} /// Unit disk graph - vertices are points, edges connect points within unit distance. #[derive(Debug, Clone, Copy, Default)] pub struct UnitDiskGraph; -impl GraphMarker for UnitDiskGraph {} - /// Bipartite graph - vertices can be partitioned into two sets with edges only between sets. #[derive(Debug, Clone, Copy, Default)] pub struct BipartiteGraph; -impl GraphMarker for BipartiteGraph {} +impl crate::variant::VariantParam for BipartiteGraph { + const CATEGORY: &'static str = "graph"; + const VALUE: &'static str = "BipartiteGraph"; + const PARENT_VALUE: Option<&'static str> = Some("SimpleGraph"); +} +inventory::submit! { + crate::variant::VariantTypeEntry { + category: "graph", + value: "BipartiteGraph", + parent: Some("SimpleGraph"), + } +} -/// Grid graph - vertices on a grid, edges to neighbors. +/// King's subgraph - a unit disk graph on a square grid with king's move connectivity. #[derive(Debug, Clone, Copy, Default)] -pub struct GridGraph; -impl GraphMarker for GridGraph {} +pub struct KingsSubgraph; -/// Triangular lattice graph - a unit disk graph on a triangular grid. +/// Triangular subgraph - a unit disk graph on a triangular lattice. #[derive(Debug, Clone, Copy, Default)] -pub struct Triangular; -impl GraphMarker for Triangular {} +pub struct TriangularSubgraph; /// Hypergraph - most general graph type. Edges can connect any number of vertices. #[derive(Debug, Clone, Copy, Default)] pub struct HyperGraph; -impl GraphMarker for HyperGraph {} - -/// Runtime registration of graph subtype relationships. -pub struct GraphSubtypeEntry { - pub subtype: &'static str, - pub supertype: &'static str, -} - -inventory::collect!(GraphSubtypeEntry); - -/// Macro to declare both compile-time trait and runtime registration. -#[macro_export] -macro_rules! declare_graph_subtype { - ($sub:ty => $sup:ty) => { - impl $crate::graph_types::GraphSubtype<$sup> for $sub {} - - ::inventory::submit! { - $crate::graph_types::GraphSubtypeEntry { - subtype: stringify!($sub), - supertype: stringify!($sup), - } - } - }; -} - -// Corrected graph type hierarchy (all transitive relationships declared for compile-time bounds). -// HyperGraph (most general) -// └── SimpleGraph -// ├── PlanarGraph -// ├── BipartiteGraph -// └── UnitDiskGraph -// └── GridGraph -declare_graph_subtype!(GridGraph => UnitDiskGraph); -declare_graph_subtype!(GridGraph => SimpleGraph); -declare_graph_subtype!(GridGraph => HyperGraph); -declare_graph_subtype!(Triangular => UnitDiskGraph); -declare_graph_subtype!(Triangular => SimpleGraph); -declare_graph_subtype!(Triangular => HyperGraph); -declare_graph_subtype!(UnitDiskGraph => SimpleGraph); -declare_graph_subtype!(UnitDiskGraph => HyperGraph); -declare_graph_subtype!(PlanarGraph => SimpleGraph); -declare_graph_subtype!(PlanarGraph => HyperGraph); -declare_graph_subtype!(BipartiteGraph => SimpleGraph); -declare_graph_subtype!(BipartiteGraph => HyperGraph); -declare_graph_subtype!(SimpleGraph => HyperGraph); - -/// 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). -#[macro_export] -macro_rules! declare_weight_subtype { - ($sub:expr => $sup:expr) => { - ::inventory::submit! { - $crate::graph_types::WeightSubtypeEntry { - subtype: $sub, - supertype: $sup, - } - } - }; -} - -// Weight type hierarchy (with transitive relationships): -// One (most restrictive) => i32 => f64 (most general) -declare_weight_subtype!("One" => "i32"); -declare_weight_subtype!("One" => "f64"); // transitive -declare_weight_subtype!("i32" => "f64"); #[cfg(test)] #[path = "unit_tests/graph_types.rs"] diff --git a/src/lib.rs b/src/lib.rs index 6c844fe9..e73c0c5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,7 @@ pub mod prelude { pub use crate::types::{ Direction, NumericSize, One, ProblemSize, SolutionSize, Unweighted, WeightElement, }; + pub use crate::variant::{CastToParent, KValue, VariantParam, K1, K2, K3, K4, K5, KN}; } // Re-export commonly used items at crate root diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index aae55b48..795eb3ba 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -6,6 +6,7 @@ use crate::registry::{FieldInfo, ProblemSchemaEntry}; use crate::topology::{Graph, SimpleGraph}; use crate::traits::{Problem, SatisfactionProblem}; +use crate::variant::{KValue, VariantParam}; use serde::{Deserialize, Serialize}; inventory::submit! { @@ -26,18 +27,19 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `K` - Number of colors (const generic) -/// * `G` - Graph type (e.g., SimpleGraph, GridGraph) +/// * `K` - KValue type representing the number of colors (e.g., K3 for 3-coloring) +/// * `G` - Graph type (e.g., SimpleGraph, KingsSubgraph) /// /// # Example /// /// ``` /// use problemreductions::models::graph::KColoring; /// use problemreductions::topology::SimpleGraph; +/// use problemreductions::variant::K3; /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // Triangle graph needs at least 3 colors -/// let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); +/// let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); /// /// let solver = BruteForce::new(); /// let solutions = solver.find_all_satisfying(&problem); @@ -48,12 +50,15 @@ inventory::submit! { /// } /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KColoring { +#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))] +pub struct KColoring { /// The underlying graph. graph: G, + #[serde(skip)] + _phantom: std::marker::PhantomData, } -impl KColoring { +impl KColoring { /// Create a new K-Coloring problem. /// /// # Arguments @@ -61,14 +66,20 @@ impl KColoring { /// * `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 } + Self { + graph, + _phantom: std::marker::PhantomData, + } } } -impl KColoring { +impl KColoring { /// Create a K-Coloring problem from an existing graph. pub fn from_graph(graph: G) -> Self { - Self { graph } + Self { + graph, + _phantom: std::marker::PhantomData, + } } /// Get a reference to the underlying graph. @@ -88,7 +99,7 @@ impl KColoring { /// Get the number of colors. pub fn num_colors(&self) -> usize { - K + K::K.expect("KN cannot be used as problem instance") } /// Get the edges as a list of (u, v) pairs. @@ -109,22 +120,20 @@ impl KColoring { } } -impl Problem for KColoring +impl Problem for KColoring where - G: Graph, + G: Graph + VariantParam, { const NAME: &'static str = "KColoring"; type Metric = bool; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("k", crate::variant::const_usize_str::()), - ("graph", G::NAME), - ] + crate::variant_params![K, G] } fn dims(&self) -> Vec { - vec![K; self.graph.num_vertices()] + let k = K::K.expect("KN cannot be used as problem instance"); + vec![k; self.graph.num_vertices()] } fn evaluate(&self, config: &[usize]) -> bool { @@ -132,7 +141,7 @@ where } } -impl SatisfactionProblem for KColoring {} +impl SatisfactionProblem for KColoring {} /// Check if a coloring is valid for a graph. pub fn is_valid_coloring( diff --git a/src/models/graph/max_cut.rs b/src/models/graph/max_cut.rs index 40328cd1..22bafadc 100644 --- a/src/models/graph/max_cut.rs +++ b/src/models/graph/max_cut.rs @@ -38,7 +38,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`, `UnitDiskGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`, `UnitDiskGraph`) /// * `W` - The weight type for edges (e.g., `i32`, `f64`) /// /// # Example @@ -193,17 +193,14 @@ impl MaxCut { impl Problem for MaxCut where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaxCut"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -219,8 +216,8 @@ where impl OptimizationProblem for MaxCut where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/maximal_is.rs b/src/models/graph/maximal_is.rs index fe05b27f..524ca9ed 100644 --- a/src/models/graph/maximal_is.rs +++ b/src/models/graph/maximal_is.rs @@ -184,17 +184,14 @@ impl MaximalIS { impl Problem for MaximalIS where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaximalIS"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -217,8 +214,8 @@ where impl OptimizationProblem for MaximalIS where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index 4b886765..02927a80 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -31,7 +31,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`, `UnitDiskGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`, `UnitDiskGraph`) /// * `W` - The weight type (e.g., `i32`, `f64`, `One`) /// /// # Example @@ -162,17 +162,14 @@ impl MaximumClique { impl Problem for MaximumClique where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaximumClique"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -195,8 +192,8 @@ where impl OptimizationProblem for MaximumClique where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index 20c75736..2453e590 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -31,7 +31,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`, `UnitDiskGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`, `UnitDiskGraph`) /// * `W` - The weight type (e.g., `i32`, `f64`, `One`) /// /// # Example @@ -162,17 +162,14 @@ impl MaximumIndependentSet { impl Problem for MaximumIndependentSet where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaximumIndependentSet"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -195,8 +192,8 @@ where impl OptimizationProblem for MaximumIndependentSet where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index b471114e..ebd66969 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -30,7 +30,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`, `UnitDiskGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`, `UnitDiskGraph`) /// * `W` - The weight type (e.g., `i32`, `f64`, `One`) /// /// # Example @@ -205,17 +205,14 @@ impl MaximumMatching { impl Problem for MaximumMatching where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaximumMatching"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -240,8 +237,8 @@ where impl OptimizationProblem for MaximumMatching where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/minimum_dominating_set.rs b/src/models/graph/minimum_dominating_set.rs index 82432804..a45aa32f 100644 --- a/src/models/graph/minimum_dominating_set.rs +++ b/src/models/graph/minimum_dominating_set.rs @@ -178,17 +178,14 @@ impl MinimumDominatingSet { impl Problem for MinimumDominatingSet where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MinimumDominatingSet"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -211,8 +208,8 @@ where impl OptimizationProblem for MinimumDominatingSet where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/minimum_vertex_cover.rs b/src/models/graph/minimum_vertex_cover.rs index 5773a023..1025927f 100644 --- a/src/models/graph/minimum_vertex_cover.rs +++ b/src/models/graph/minimum_vertex_cover.rs @@ -145,17 +145,14 @@ impl MinimumVertexCover { impl Problem for MinimumVertexCover where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MinimumVertexCover"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -178,8 +175,8 @@ where impl OptimizationProblem for MinimumVertexCover where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/graph/traveling_salesman.rs b/src/models/graph/traveling_salesman.rs index 494bde83..5bfbaf54 100644 --- a/src/models/graph/traveling_salesman.rs +++ b/src/models/graph/traveling_salesman.rs @@ -41,7 +41,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`) /// * `W` - The weight type for edges (e.g., `i32`, `f64`) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TravelingSalesman { @@ -165,17 +165,14 @@ impl TravelingSalesman { impl Problem for TravelingSalesman where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "TravelingSalesman"; type Metric = SolutionSize; fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } fn dims(&self) -> Vec { @@ -200,8 +197,8 @@ where impl OptimizationProblem for TravelingSalesman where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/optimization/ilp.rs b/src/models/optimization/ilp.rs index 14e708e0..dda8bf7e 100644 --- a/src/models/optimization/ilp.rs +++ b/src/models/optimization/ilp.rs @@ -351,7 +351,7 @@ impl Problem for ILP { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/optimization/qubo.rs b/src/models/optimization/qubo.rs index 803248f7..d3bb01c3 100644 --- a/src/models/optimization/qubo.rs +++ b/src/models/optimization/qubo.rs @@ -146,6 +146,7 @@ where impl Problem for QUBO where W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero @@ -165,13 +166,14 @@ where } fn variant() -> Vec<(&'static str, &'static str)> { - vec![("weight", crate::variant::short_type_name::())] + crate::variant_params![W] } } impl OptimizationProblem for QUBO where W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero diff --git a/src/models/optimization/spin_glass.rs b/src/models/optimization/spin_glass.rs index c717b44c..3e546058 100644 --- a/src/models/optimization/spin_glass.rs +++ b/src/models/optimization/spin_glass.rs @@ -36,7 +36,7 @@ inventory::submit! { /// /// # Type Parameters /// -/// * `G` - The graph type (e.g., `SimpleGraph`, `GridGraph`, `UnitDiskGraph`) +/// * `G` - The graph type (e.g., `SimpleGraph`, `KingsSubgraph`, `UnitDiskGraph`) /// * `W` - The weight type for couplings (e.g., `i32`, `f64`) /// /// # Example @@ -197,8 +197,9 @@ where impl Problem for SpinGlass where - G: Graph, + G: Graph + crate::variant::VariantParam, W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero @@ -220,17 +221,15 @@ where } fn variant() -> Vec<(&'static str, &'static str)> { - vec![ - ("graph", G::NAME), - ("weight", crate::variant::short_type_name::()), - ] + crate::variant_params![G, W] } } impl OptimizationProblem for SpinGlass where - G: Graph, + G: Graph + crate::variant::VariantParam, W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero diff --git a/src/models/satisfiability/ksat.rs b/src/models/satisfiability/ksat.rs index 1ffa921e..937eeb9d 100644 --- a/src/models/satisfiability/ksat.rs +++ b/src/models/satisfiability/ksat.rs @@ -7,6 +7,7 @@ use crate::registry::{FieldInfo, ProblemSchemaEntry}; use crate::traits::{Problem, SatisfactionProblem}; +use crate::variant::KValue; use serde::{Deserialize, Serialize}; use super::CNFClause; @@ -31,16 +32,17 @@ inventory::submit! { /// This is the decision version of the problem. /// /// # Type Parameters -/// * `K` - The number of literals per clause (compile-time constant) +/// * `K` - A type implementing `KValue` that specifies the number of literals per clause /// /// # Example /// /// ``` /// use problemreductions::models::satisfiability::{KSatisfiability, CNFClause}; +/// use problemreductions::variant::K3; /// use problemreductions::{Problem, Solver, BruteForce}; /// /// // 3-SAT formula: (x1 OR x2 OR x3) AND (NOT x1 OR x2 OR NOT x3) -/// let problem = KSatisfiability::<3>::new( +/// let problem = KSatisfiability::::new( /// 3, /// vec![ /// CNFClause::new(vec![1, 2, 3]), // x1 OR x2 OR x3 @@ -53,29 +55,38 @@ inventory::submit! { /// assert!(!solutions.is_empty()); /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KSatisfiability { +#[serde(bound(deserialize = ""))] +pub struct KSatisfiability { /// Number of variables. num_vars: usize, /// Clauses in CNF, each with exactly K literals. clauses: Vec, + #[serde(skip)] + _phantom: std::marker::PhantomData, } -impl KSatisfiability { +impl KSatisfiability { /// Create a new K-SAT problem. /// /// # Panics - /// Panics if any clause does not have exactly K literals. + /// Panics if any clause does not have exactly K literals, + /// or if K is KN (generic K cannot be instantiated). pub fn new(num_vars: usize, clauses: Vec) -> Self { + let k = K::K.expect("KN cannot be instantiated"); for (i, clause) in clauses.iter().enumerate() { assert!( - clause.len() == K, + clause.len() == k, "Clause {} has {} literals, expected {}", i, clause.len(), - K + k ); } - Self { num_vars, clauses } + Self { + num_vars, + clauses, + _phantom: std::marker::PhantomData, + } } /// Create a new K-SAT problem allowing clauses with fewer than K literals. @@ -84,18 +95,24 @@ impl KSatisfiability { /// fewer literals (e.g., when allow_less is true in the Julia implementation). /// /// # Panics - /// Panics if any clause has more than K literals. + /// Panics if any clause has more than K literals, + /// or if K is KN (generic K cannot be instantiated). pub fn new_allow_less(num_vars: usize, clauses: Vec) -> Self { + let k = K::K.expect("KN cannot be instantiated"); for (i, clause) in clauses.iter().enumerate() { assert!( - clause.len() <= K, + clause.len() <= k, "Clause {} has {} literals, expected at most {}", i, clause.len(), - K + k ); } - Self { num_vars, clauses } + Self { + num_vars, + clauses, + _phantom: std::marker::PhantomData, + } } /// Get the number of variables. @@ -137,7 +154,7 @@ impl KSatisfiability { } } -impl Problem for KSatisfiability { +impl Problem for KSatisfiability { const NAME: &'static str = "KSatisfiability"; type Metric = bool; @@ -151,11 +168,11 @@ impl Problem for KSatisfiability { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![("k", crate::variant::const_usize_str::())] + crate::variant_params![K] } } -impl SatisfactionProblem for KSatisfiability {} +impl SatisfactionProblem for KSatisfiability {} #[cfg(test)] #[path = "../../unit_tests/models/satisfiability/ksat.rs"] diff --git a/src/models/satisfiability/sat.rs b/src/models/satisfiability/sat.rs index b27b2bf4..9f506638 100644 --- a/src/models/satisfiability/sat.rs +++ b/src/models/satisfiability/sat.rs @@ -182,7 +182,7 @@ impl Problem for Satisfiability { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/set/maximum_set_packing.rs b/src/models/set/maximum_set_packing.rs index 9ace61a7..96ab522b 100644 --- a/src/models/set/maximum_set_packing.rs +++ b/src/models/set/maximum_set_packing.rs @@ -121,7 +121,7 @@ impl MaximumSetPacking { impl Problem for MaximumSetPacking where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MaximumSetPacking"; type Metric = SolutionSize; @@ -144,13 +144,13 @@ where } fn variant() -> Vec<(&'static str, &'static str)> { - vec![("weight", crate::variant::short_type_name::())] + crate::variant_params![W] } } impl OptimizationProblem for MaximumSetPacking where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/set/minimum_set_covering.rs b/src/models/set/minimum_set_covering.rs index 01d4fac2..f3569c36 100644 --- a/src/models/set/minimum_set_covering.rs +++ b/src/models/set/minimum_set_covering.rs @@ -131,7 +131,7 @@ impl MinimumSetCovering { impl Problem for MinimumSetCovering where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { const NAME: &'static str = "MinimumSetCovering"; type Metric = SolutionSize; @@ -157,13 +157,13 @@ where } fn variant() -> Vec<(&'static str, &'static str)> { - vec![("weight", crate::variant::short_type_name::())] + crate::variant_params![W] } } impl OptimizationProblem for MinimumSetCovering where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Value = W::Sum; diff --git a/src/models/specialized/biclique_cover.rs b/src/models/specialized/biclique_cover.rs index 4f9492dd..4e13627c 100644 --- a/src/models/specialized/biclique_cover.rs +++ b/src/models/specialized/biclique_cover.rs @@ -226,7 +226,7 @@ impl Problem for BicliqueCover { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/specialized/bmf.rs b/src/models/specialized/bmf.rs index e546ac4c..3bc1f02f 100644 --- a/src/models/specialized/bmf.rs +++ b/src/models/specialized/bmf.rs @@ -206,7 +206,7 @@ impl Problem for BMF { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/specialized/circuit.rs b/src/models/specialized/circuit.rs index ddc1d5dd..460f22e6 100644 --- a/src/models/specialized/circuit.rs +++ b/src/models/specialized/circuit.rs @@ -279,7 +279,7 @@ impl Problem for CircuitSAT { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/specialized/factoring.rs b/src/models/specialized/factoring.rs index 490f3025..4a30192c 100644 --- a/src/models/specialized/factoring.rs +++ b/src/models/specialized/factoring.rs @@ -134,7 +134,7 @@ impl Problem for Factoring { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/models/specialized/paintshop.rs b/src/models/specialized/paintshop.rs index 84c104c2..c78b7e86 100644 --- a/src/models/specialized/paintshop.rs +++ b/src/models/specialized/paintshop.rs @@ -174,7 +174,7 @@ impl Problem for PaintShop { } fn variant() -> Vec<(&'static str, &'static str)> { - vec![] + crate::variant_params![] } } diff --git a/src/polynomial.rs b/src/polynomial.rs index 60436b2d..58fd7225 100644 --- a/src/polynomial.rs +++ b/src/polynomial.rs @@ -5,7 +5,7 @@ use std::fmt; use std::ops::Add; /// A monomial: coefficient × Π(variable^exponent) -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, serde::Serialize)] pub struct Monomial { pub coefficient: f64, pub variables: Vec<(&'static str, u8)>, @@ -52,7 +52,7 @@ impl Monomial { } /// A polynomial: Σ monomials -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, serde::Serialize)] pub struct Polynomial { pub terms: Vec, } diff --git a/src/rules/coloring_ilp.rs b/src/rules/coloring_ilp.rs index f35d539d..aa708845 100644 --- a/src/rules/coloring_ilp.rs +++ b/src/rules/coloring_ilp.rs @@ -14,6 +14,7 @@ use crate::reduction; use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::{Graph, SimpleGraph}; +use crate::variant::{KValue, K1, K2, K3, K4, KN}; /// Result of reducing KColoring to ILP. /// @@ -22,22 +23,23 @@ use crate::topology::{Graph, SimpleGraph}; /// - Constraints ensure each vertex has exactly one color /// - Constraints ensure adjacent vertices have different colors #[derive(Debug, Clone)] -pub struct ReductionKColoringToILP { +pub struct ReductionKColoringToILP { target: ILP, num_vertices: usize, - _phantom: std::marker::PhantomData, + _phantom: std::marker::PhantomData<(K, G)>, } -impl ReductionKColoringToILP { +impl ReductionKColoringToILP { /// Get the variable index for vertex v with color c. fn var_index(&self, vertex: usize, color: usize) -> usize { - vertex * K + color + let k = K::K.expect("KN cannot be used as problem instance"); + vertex * k + color } } -impl ReductionResult for ReductionKColoringToILP +impl ReductionResult for ReductionKColoringToILP where - G: Graph, + G: Graph + crate::variant::VariantParam, { type Source = KColoring; type Target = ILP; @@ -51,9 +53,10 @@ where /// The ILP solution has num_vertices * K binary variables. /// For each vertex, we find which color has value 1. fn extract_solution(&self, target_solution: &[usize]) -> Vec { + let k = K::K.expect("KN cannot be used as problem instance"); (0..self.num_vertices) .map(|v| { - (0..K) + (0..k) .find(|&c| { let var_idx = self.var_index(v, c); var_idx < target_solution.len() && target_solution[var_idx] == 1 @@ -64,6 +67,60 @@ where } } +/// Helper function implementing the KColoring to ILP reduction logic. +fn reduce_kcoloring_to_ilp( + problem: &KColoring, +) -> ReductionKColoringToILP { + let k = K::K.expect("KN cannot be used as problem instance"); + let num_vertices = problem.num_vertices(); + let num_vars = num_vertices * k; + + // Helper function to get variable index + let var_index = |v: usize, c: usize| -> usize { v * k + c }; + + // All variables are binary (0 or 1) + let bounds = vec![VarBounds::binary(); num_vars]; + + let mut constraints = Vec::new(); + + // Constraint 1: Each vertex has exactly one color + // sum_c x_{v,c} = 1 for each vertex v + for v in 0..num_vertices { + let terms: Vec<(usize, f64)> = (0..k).map(|c| (var_index(v, c), 1.0)).collect(); + constraints.push(LinearConstraint::eq(terms, 1.0)); + } + + // Constraint 2: Adjacent vertices have different colors + // x_{u,c} + x_{v,c} <= 1 for each edge (u,v) and each color c + for (u, v) in problem.edges() { + for c in 0..k { + constraints.push(LinearConstraint::le( + vec![(var_index(u, c), 1.0), (var_index(v, c), 1.0)], + 1.0, + )); + } + } + + // Objective: minimize 0 (feasibility problem) + // We use an empty objective + let objective: Vec<(usize, f64)> = vec![]; + + let target = ILP::new( + num_vars, + bounds, + constraints, + objective, + ObjectiveSense::Minimize, + ); + + ReductionKColoringToILP { + target, + num_vertices, + _phantom: std::marker::PhantomData, + } +} + +// Register only the KN variant in the reduction graph #[reduction( overhead = { ReductionOverhead::new(vec![ @@ -72,61 +129,28 @@ where ]) } )] -impl ReduceTo for KColoring { - type Result = ReductionKColoringToILP; +impl ReduceTo for KColoring { + type Result = ReductionKColoringToILP; fn reduce_to(&self) -> Self::Result { - let num_vertices = self.num_vertices(); - let num_vars = num_vertices * K; - - // Helper function to get variable index - let var_index = |v: usize, c: usize| -> usize { v * K + c }; - - // All variables are binary (0 or 1) - let bounds = vec![VarBounds::binary(); num_vars]; - - let mut constraints = Vec::new(); - - // Constraint 1: Each vertex has exactly one color - // sum_c x_{v,c} = 1 for each vertex v - for v in 0..num_vertices { - let terms: Vec<(usize, f64)> = (0..K).map(|c| (var_index(v, c), 1.0)).collect(); - constraints.push(LinearConstraint::eq(terms, 1.0)); - } - - // Constraint 2: Adjacent vertices have different colors - // x_{u,c} + x_{v,c} <= 1 for each edge (u,v) and each color c - for (u, v) in self.edges() { - for c in 0..K { - constraints.push(LinearConstraint::le( - vec![(var_index(u, c), 1.0), (var_index(v, c), 1.0)], - 1.0, - )); - } - } + reduce_kcoloring_to_ilp(self) + } +} - // Objective: minimize 0 (feasibility problem) - // We use an empty objective - let objective: Vec<(usize, f64)> = vec![]; - - let target = ILP::new( - num_vars, - bounds, - constraints, - objective, - ObjectiveSense::Minimize, - ); - - ReductionKColoringToILP { - target, - num_vertices, - _phantom: std::marker::PhantomData, +// Additional concrete impls for tests (not registered in reduction graph) +macro_rules! impl_kcoloring_to_ilp { + ($($ktype:ty),+) => {$( + impl ReduceTo for KColoring<$ktype, SimpleGraph> { + type Result = ReductionKColoringToILP<$ktype, SimpleGraph>; + fn reduce_to(&self) -> Self::Result { reduce_kcoloring_to_ilp(self) } } - } + )+}; } +impl_kcoloring_to_ilp!(K1, K2, K3, K4); + // Keep the old type alias for backwards compatibility -pub type ReductionColoringToILP = ReductionKColoringToILP<3, SimpleGraph>; +pub type ReductionColoringToILP = ReductionKColoringToILP; #[cfg(test)] #[path = "../unit_tests/rules/coloring_ilp.rs"] diff --git a/src/rules/coloring_qubo.rs b/src/rules/coloring_qubo.rs index 6e0649f4..b0cda91b 100644 --- a/src/rules/coloring_qubo.rs +++ b/src/rules/coloring_qubo.rs @@ -3,10 +3,10 @@ //! One-hot encoding: x_{v,c} = 1 iff vertex v gets color c. //! QUBO variable index: v * K + c. //! -//! One-hot penalty: P₁·Σ_v (1 - Σ_c x_{v,c})² -//! Edge penalty: P₂·Σ_{(u,v)∈E} Σ_c x_{u,c}·x_{v,c} +//! One-hot penalty: P1*sum_v (1 - sum_c x_{v,c})^2 +//! Edge penalty: P2*sum_{(u,v) in E} sum_c x_{u,c}*x_{v,c} //! -//! QUBO has n·K variables. +//! QUBO has n*K variables. use crate::models::graph::KColoring; use crate::models::optimization::QUBO; @@ -15,14 +15,17 @@ use crate::reduction; use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::SimpleGraph; +use crate::variant::{KValue, K2, K3, KN}; + /// Result of reducing KColoring to QUBO. #[derive(Debug, Clone)] -pub struct ReductionKColoringToQUBO { +pub struct ReductionKColoringToQUBO { target: QUBO, num_vertices: usize, + _phantom: std::marker::PhantomData, } -impl ReductionResult for ReductionKColoringToQUBO { +impl ReductionResult for ReductionKColoringToQUBO { type Source = KColoring; type Target = QUBO; @@ -32,75 +35,98 @@ impl ReductionResult for ReductionKColoringToQUBO { /// Decode one-hot: for each vertex, find which color bit is 1. fn extract_solution(&self, target_solution: &[usize]) -> Vec { + let k = K::K.expect("KN cannot be used as problem instance"); (0..self.num_vertices) .map(|v| { - (0..K) - .find(|&c| target_solution[v * K + c] == 1) + (0..k) + .find(|&c| target_solution[v * k + c] == 1) .unwrap_or(0) }) .collect() } } -#[reduction( - overhead = { ReductionOverhead::new(vec![("num_vars", poly!(num_vertices * num_colors))]) } -)] -impl ReduceTo> for KColoring { - type Result = ReductionKColoringToQUBO; +/// Helper function implementing the KColoring to QUBO reduction logic. +fn reduce_kcoloring_to_qubo( + problem: &KColoring, +) -> ReductionKColoringToQUBO { + let k = K::K.expect("KN cannot be used as problem instance"); + let n = problem.num_vertices(); + let edges = problem.edges(); + let nq = n * k; - fn reduce_to(&self) -> Self::Result { - let n = self.num_vertices(); - let edges = self.edges(); - let nq = n * K; - - // Penalty must be large enough to enforce one-hot constraints - // P1 for one-hot, P2 for edge conflicts; use same penalty - let penalty = 1.0 + n as f64; - - let mut matrix = vec![vec![0.0; nq]; nq]; - - // One-hot penalty: P₁·Σ_v (1 - Σ_c x_{v,c})² - // Expanding: (1 - Σ_c x_{v,c})² = 1 - 2·Σ_c x_{v,c} + (Σ_c x_{v,c})² - // = 1 - 2·Σ_c x_{v,c} + Σ_c x_{v,c}² + 2·Σ_{c> for KColoring { + type Result = ReductionKColoringToQUBO; + + fn reduce_to(&self) -> Self::Result { + reduce_kcoloring_to_qubo(self) + } +} + +// Additional concrete impls for tests (not registered in reduction graph) +macro_rules! impl_kcoloring_to_qubo { + ($($ktype:ty),+) => {$( + impl ReduceTo> for KColoring<$ktype, SimpleGraph> { + type Result = ReductionKColoringToQUBO<$ktype>; + fn reduce_to(&self) -> Self::Result { reduce_kcoloring_to_qubo(self) } + } + )+}; +} + +impl_kcoloring_to_qubo!(K2, K3); + #[cfg(test)] #[path = "../unit_tests/rules/coloring_qubo.rs"] mod tests; diff --git a/src/rules/graph.rs b/src/rules/graph.rs index 6b4649a3..6f6b6555 100644 --- a/src/rules/graph.rs +++ b/src/rules/graph.rs @@ -4,14 +4,14 @@ //! for topology, allowing path finding regardless of weight type parameters. //! //! This module implements set-theoretic validation for path finding: -//! - Graph hierarchy is built from `GraphSubtypeEntry` registrations +//! - Variant hierarchy is built from `VariantTypeEntry` registrations //! - Reduction applicability uses subtype relationships: A <= C and D <= B //! - Dijkstra's algorithm with custom cost functions for optimal paths -use crate::graph_types::{GraphSubtypeEntry, WeightSubtypeEntry}; use crate::rules::cost::PathCostFn; use crate::rules::registry::{ReductionEntry, ReductionOverhead}; use crate::types::ProblemSize; +use crate::variant::VariantTypeEntry; use ordered_float::OrderedFloat; use petgraph::algo::all_simple_paths; use petgraph::graph::{DiGraph, NodeIndex}; @@ -54,6 +54,16 @@ pub struct NodeJson { pub doc_path: String, } +/// A matched reduction entry returned by [`ReductionGraph::find_best_entry`]. +pub struct MatchedEntry { + /// The entry's source variant (may be less specific than the caller's current variant). + pub source_variant: std::collections::BTreeMap, + /// The entry's target variant (becomes the new current variant after the reduction). + pub target_variant: std::collections::BTreeMap, + /// The overhead of the reduction. + pub overhead: ReductionOverhead, +} + /// Internal reference to a problem variant, used during edge construction. #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct VariantRef { @@ -116,6 +126,68 @@ impl ReductionPath { } } +/// A node in a variant-level reduction path. +#[derive(Debug, Clone, Serialize)] +pub struct ReductionStep { + /// Problem name (e.g., "MaximumIndependentSet"). + pub name: String, + /// Variant at this point (e.g., {"graph": "KingsSubgraph", "weight": "i32"}). + pub variant: std::collections::BTreeMap, +} + +/// The kind of transition between adjacent steps in a resolved path. +#[derive(Debug, Clone, Serialize)] +pub enum EdgeKind { + /// A registered reduction (backed by a ReduceTo impl). + Reduction { + /// Overhead from the matching ReductionEntry. + overhead: ReductionOverhead, + }, + /// A natural cast via subtype relaxation. Identity overhead. + NaturalCast, +} + +/// A fully resolved reduction path with variant information at each node. +/// +/// Created by [`ReductionGraph::resolve_path`] from a name-level [`ReductionPath`]. +/// Each adjacent pair of steps is connected by an [`EdgeKind`]: either a registered +/// reduction or a natural cast (subtype relaxation with identity overhead). +#[derive(Debug, Clone, Serialize)] +pub struct ResolvedPath { + /// Sequence of (name, variant) nodes. + pub steps: Vec, + /// Edge kinds between adjacent steps. Length = steps.len() - 1. + pub edges: Vec, +} + +impl ResolvedPath { + /// Number of edges (reductions + casts) in the path. + pub fn len(&self) -> usize { + self.edges.len() + } + + /// Whether the path is empty. + pub fn is_empty(&self) -> bool { + self.edges.is_empty() + } + + /// Number of registered reduction steps (excludes natural casts). + pub fn num_reductions(&self) -> usize { + self.edges + .iter() + .filter(|e| matches!(e, EdgeKind::Reduction { .. })) + .count() + } + + /// Number of natural cast steps. + pub fn num_casts(&self) -> usize { + self.edges + .iter() + .filter(|e| matches!(e, EdgeKind::NaturalCast)) + .count() + } +} + /// Edge data for a reduction. #[derive(Clone, Debug)] pub struct ReductionEdge { @@ -159,7 +231,7 @@ impl ReductionEdge { /// /// The graph supports: /// - Auto-discovery of reductions from `inventory::iter::` -/// - Graph hierarchy from `inventory::iter::` +/// - Variant hierarchy from `inventory::iter::` /// - Set-theoretic validation for path finding /// - Dijkstra with custom cost functions pub struct ReductionGraph { @@ -167,10 +239,8 @@ pub struct ReductionGraph { graph: DiGraph<&'static str, ReductionEdge>, /// Map from base type name to node index. name_indices: HashMap<&'static str, NodeIndex>, - /// Graph hierarchy: subtype -> set of supertypes (transitively closed). - graph_hierarchy: HashMap<&'static str, HashSet<&'static str>>, - /// Weight hierarchy: subtype -> set of supertypes (transitively closed). - weight_hierarchy: HashMap<&'static str, HashSet<&'static str>>, + /// Variant hierarchy: category -> (value -> set_of_supertypes). Transitively closed. + variant_hierarchy: HashMap<&'static str, HashMap<&'static str, HashSet<&'static str>>>, } impl ReductionGraph { @@ -179,11 +249,8 @@ impl ReductionGraph { let mut graph = DiGraph::new(); let mut name_indices = HashMap::new(); - // Build graph hierarchy from GraphSubtypeEntry registrations - let graph_hierarchy = Self::build_graph_hierarchy(); - - // Build weight hierarchy from WeightSubtypeEntry registrations - let weight_hierarchy = Self::build_weight_hierarchy(); + // Build unified variant hierarchy from all sources + let variant_hierarchy = Self::build_variant_hierarchy(); // Register reductions from inventory (auto-discovery) for entry in inventory::iter:: { @@ -219,120 +286,102 @@ impl ReductionGraph { Self { graph, name_indices, - graph_hierarchy, - weight_hierarchy, + variant_hierarchy, } } - /// Build graph hierarchy from GraphSubtypeEntry registrations. - /// Computes the transitive closure of the subtype relationship. - fn build_graph_hierarchy() -> HashMap<&'static str, HashSet<&'static str>> { - let mut supertypes: HashMap<&'static str, HashSet<&'static str>> = HashMap::new(); - - // Collect direct subtype relationships - for entry in inventory::iter:: { - supertypes - .entry(entry.subtype) - .or_default() - .insert(entry.supertype); - } - - // Compute transitive closure - 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; + /// Build unified variant hierarchy from `VariantTypeEntry` registrations. + /// + /// Collects all `VariantTypeEntry` registrations and computes transitive closure + /// of the parent relationships for each category. + fn build_variant_hierarchy( + ) -> HashMap<&'static str, HashMap<&'static str, HashSet<&'static str>>> { + let mut hierarchy: HashMap<&'static str, HashMap<&'static str, HashSet<&'static str>>> = + HashMap::new(); + + // Collect from VariantTypeEntry (all categories) + for entry in inventory::iter:: { + if let Some(parent) = entry.parent { + hierarchy + .entry(entry.category) + .or_default() + .entry(entry.value) + .or_default() + .insert(parent); } } - supertypes - } - - /// Build weight hierarchy from WeightSubtypeEntry registrations. - /// Computes the transitive closure of the subtype relationship. - fn build_weight_hierarchy() -> HashMap<&'static str, HashSet<&'static str>> { - let mut supertypes: HashMap<&'static str, HashSet<&'static str>> = HashMap::new(); - - // Collect direct subtype relationships - for entry in inventory::iter:: { - supertypes - .entry(entry.subtype) - .or_default() - .insert(entry.supertype); - } - - // Compute transitive closure - 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; + // Compute transitive closure for each category + for supertypes in hierarchy.values_mut() { + 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; + if !changed { + break; + } } } - supertypes + hierarchy } - /// Check if `sub` is a subtype of `sup` (or equal). - pub fn is_graph_subtype(&self, sub: &str, sup: &str) -> bool { + /// Check if `sub` is a subtype of `sup` (or equal) within the given category. + fn is_subtype(&self, category: &str, sub: &str, sup: &str) -> bool { sub == sup || self - .graph_hierarchy - .get(sub) - .map(|s| s.contains(sup)) + .variant_hierarchy + .get(category) + .and_then(|cat| cat.get(sub)) + .map(|supers| supers.contains(sup)) .unwrap_or(false) } - /// Get the weight hierarchy (for inspection/testing). - pub fn weight_hierarchy(&self) -> &HashMap<&'static str, HashSet<&'static str>> { - &self.weight_hierarchy + /// Check if `sub` is a graph subtype of `sup` (or equal). + pub fn is_graph_subtype(&self, sub: &str, sup: &str) -> bool { + self.is_subtype("graph", sub, sup) } /// Check if `sub` is a weight subtype of `sup` (or equal). 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) + self.is_subtype("weight", sub, sup) + } + + /// Check if `sub` is a K subtype of `sup` (or equal). + pub fn is_k_subtype(&self, sub: &str, sup: &str) -> bool { + self.is_subtype("k", sub, sup) + } + + /// Get the graph hierarchy (for inspection/testing). + pub fn graph_hierarchy(&self) -> &HashMap<&'static str, HashSet<&'static str>> { + // If "graph" category is absent, we return a static empty map. + static EMPTY: std::sync::LazyLock>> = + std::sync::LazyLock::new(HashMap::new); + self.variant_hierarchy.get("graph").unwrap_or(&EMPTY) + } + + /// Get the weight hierarchy (for inspection/testing). + pub fn weight_hierarchy(&self) -> &HashMap<&'static str, HashSet<&'static str>> { + static EMPTY: std::sync::LazyLock>> = + std::sync::LazyLock::new(HashMap::new); + self.variant_hierarchy.get("weight").unwrap_or(&EMPTY) } /// Check if a reduction rule can be used. @@ -538,11 +587,6 @@ impl ReductionGraph { pub fn num_reductions(&self) -> usize { self.graph.edge_count() } - - /// Get the graph hierarchy (for inspection/testing). - pub fn graph_hierarchy(&self) -> &HashMap<&'static str, HashSet<&'static str>> { - &self.graph_hierarchy - } } impl Default for ReductionGraph { @@ -551,12 +595,6 @@ 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, @@ -583,13 +621,8 @@ impl ReductionGraph { 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), - "k" => is_const_subtype(a_val, b_val), - _ => false, // Unknown fields must be equal - }; + // Check subtype relationship using the unified hierarchy + let is_sub = self.is_subtype(key.as_str(), a_val, b_val); if !is_sub { all_compatible = false; @@ -625,6 +658,131 @@ impl ReductionGraph { } } + /// Find the best matching `ReductionEntry` for a (source_name, target_name) pair + /// given the caller's current source variant. + /// + /// "Best" means: compatible (current variant is reducible to the entry's source variant) + /// and most specific (tightest fit among all compatible entries). + /// + /// Returns `(entry_source_variant, entry_target_variant, overhead)` or `None`. + pub fn find_best_entry( + &self, + source_name: &str, + target_name: &str, + current_variant: &std::collections::BTreeMap, + ) -> Option { + let mut best: Option = None; + + for entry in inventory::iter:: { + if entry.source_name != source_name || entry.target_name != target_name { + continue; + } + + let entry_source = Self::variant_to_map(&entry.source_variant()); + let entry_target = Self::variant_to_map(&entry.target_variant()); + + // Check: current_variant is reducible to entry's source variant + // (current is equal-or-more-specific on every axis) + if current_variant != &entry_source + && !self.is_variant_reducible(current_variant, &entry_source) + { + continue; + } + + // Pick the most specific: if we already have a best, prefer the one + // whose source_variant is more specific (tighter fit). + // is_variant_reducible(A, B) means A ≤ B (A is subtype of B), + // so entry_source ≤ best_source means the new entry is more specific. + let dominated = if let Some(ref best_entry) = best { + self.is_variant_reducible(&entry_source, &best_entry.source_variant) + || entry_source == *current_variant + } else { + true + }; + + if dominated { + best = Some(MatchedEntry { + source_variant: entry_source, + target_variant: entry_target, + overhead: entry.overhead(), + }); + } + } + + best + } + + /// Resolve a name-level [`ReductionPath`] into a variant-level [`ResolvedPath`]. + /// + /// Walks the name-level path, threading variant state through each edge. + /// For each step, picks the most-specific compatible `ReductionEntry` and + /// inserts `NaturalCast` steps where the caller's variant is more specific + /// than the rule's expected source variant. + /// + /// Returns `None` if no compatible reduction entry exists for any step. + pub fn resolve_path( + &self, + path: &ReductionPath, + source_variant: &std::collections::BTreeMap, + target_variant: &std::collections::BTreeMap, + ) -> Option { + if path.type_names.len() < 2 { + return None; + } + + let mut current_variant = source_variant.clone(); + let mut steps = vec![ReductionStep { + name: path.type_names[0].to_string(), + variant: current_variant.clone(), + }]; + let mut edges = Vec::new(); + + for i in 0..path.type_names.len() - 1 { + let src_name = path.type_names[i]; + let dst_name = path.type_names[i + 1]; + + let matched = self.find_best_entry(src_name, dst_name, ¤t_variant)?; + + // Insert natural cast if current variant differs from entry's source. + // Safety: find_best_entry already verified is_variant_reducible(current, entry_source). + if current_variant != matched.source_variant { + debug_assert!( + self.is_variant_reducible(¤t_variant, &matched.source_variant), + "natural cast requires current variant to be a subtype of entry source" + ); + steps.push(ReductionStep { + name: src_name.to_string(), + variant: matched.source_variant, + }); + edges.push(EdgeKind::NaturalCast); + } + + // Advance through the reduction + current_variant = matched.target_variant; + steps.push(ReductionStep { + name: dst_name.to_string(), + variant: current_variant.clone(), + }); + edges.push(EdgeKind::Reduction { + overhead: matched.overhead, + }); + } + + // Trailing natural cast if final variant differs from requested target + if current_variant != *target_variant + && self.is_variant_reducible(¤t_variant, target_variant) + { + let last_name = path.type_names.last().unwrap(); + steps.push(ReductionStep { + name: last_name.to_string(), + variant: target_variant.clone(), + }); + edges.push(EdgeKind::NaturalCast); + } + + Some(ResolvedPath { steps, edges }) + } + /// Export the reduction graph as a JSON-serializable structure. /// /// This method generates nodes for each variant based on the registered reductions. @@ -661,6 +819,15 @@ impl ReductionGraph { )); } + // Remove empty-variant base nodes that are redundant (same name already has specific variants) + let names_with_variants: HashSet = node_set + .iter() + .filter(|(_, variant)| !variant.is_empty()) + .map(|(name, _)| name.clone()) + .collect(); + node_set + .retain(|(name, variant)| !variant.is_empty() || !names_with_variants.contains(name)); + // Build nodes with categories and doc paths derived from ProblemSchemaEntry.module_path let mut nodes: Vec = node_set .iter() diff --git a/src/rules/ksatisfiability_qubo.rs b/src/rules/ksatisfiability_qubo.rs index c6359343..2cfb4f98 100644 --- a/src/rules/ksatisfiability_qubo.rs +++ b/src/rules/ksatisfiability_qubo.rs @@ -18,6 +18,7 @@ use crate::poly; use crate::reduction; use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; +use crate::variant::{K2, K3}; /// Result of reducing KSatisfiability to QUBO. #[derive(Debug, Clone)] pub struct ReductionKSatToQUBO { @@ -26,7 +27,7 @@ pub struct ReductionKSatToQUBO { } impl ReductionResult for ReductionKSatToQUBO { - type Source = KSatisfiability<2>; + type Source = KSatisfiability; type Target = QUBO; fn target_problem(&self) -> &Self::Target { @@ -38,7 +39,7 @@ impl ReductionResult for ReductionKSatToQUBO { } } -/// Result of reducing KSatisfiability<3> to QUBO. +/// Result of reducing `KSatisfiability` to QUBO. #[derive(Debug, Clone)] pub struct Reduction3SATToQUBO { target: QUBO, @@ -46,7 +47,7 @@ pub struct Reduction3SATToQUBO { } impl ReductionResult for Reduction3SATToQUBO { - type Source = KSatisfiability<3>; + type Source = KSatisfiability; type Target = QUBO; fn target_problem(&self) -> &Self::Target { @@ -294,7 +295,7 @@ fn build_qubo_matrix( #[reduction( overhead = { ReductionOverhead::new(vec![("num_vars", poly!(num_vars))]) } )] -impl ReduceTo> for KSatisfiability<2> { +impl ReduceTo> for KSatisfiability { type Result = ReductionKSatToQUBO; fn reduce_to(&self) -> Self::Result { @@ -313,7 +314,7 @@ impl ReduceTo> for KSatisfiability<2> { ("num_vars", poly!(num_vars) + poly!(num_clauses)), ]) } )] -impl ReduceTo> for KSatisfiability<3> { +impl ReduceTo> for KSatisfiability { type Result = Reduction3SATToQUBO; fn reduce_to(&self) -> Self::Result { diff --git a/src/rules/maximumindependentset_gridgraph.rs b/src/rules/maximumindependentset_gridgraph.rs index 62ac6b65..92cc6cf4 100644 --- a/src/rules/maximumindependentset_gridgraph.rs +++ b/src/rules/maximumindependentset_gridgraph.rs @@ -1,4 +1,4 @@ -//! Reduction from MaximumIndependentSet on SimpleGraph/UnitDiskGraph to GridGraph +//! Reduction from MaximumIndependentSet on SimpleGraph/UnitDiskGraph to KingsSubgraph //! using the King's Subgraph (KSG) unit disk mapping. //! //! Maps an arbitrary graph's MIS problem to an equivalent weighted MIS on a grid graph. @@ -9,18 +9,18 @@ use crate::reduction; use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::rules::unitdiskmapping::ksg; -use crate::topology::{GridGraph, SimpleGraph, UnitDiskGraph}; +use crate::topology::{KingsSubgraph, SimpleGraph, UnitDiskGraph}; -/// Result of reducing MIS on SimpleGraph to MIS on GridGraph. +/// Result of reducing MIS on SimpleGraph to MIS on KingsSubgraph. #[derive(Debug, Clone)] pub struct ReductionISSimpleToGrid { - target: MaximumIndependentSet, i32>, + target: MaximumIndependentSet, mapping_result: ksg::MappingResult, } impl ReductionResult for ReductionISSimpleToGrid { type Source = MaximumIndependentSet; - type Target = MaximumIndependentSet, i32>; + type Target = MaximumIndependentSet; fn target_problem(&self) -> &Self::Target { &self.target @@ -39,7 +39,7 @@ impl ReductionResult for ReductionISSimpleToGrid { ]) } )] -impl ReduceTo, i32>> +impl ReduceTo> for MaximumIndependentSet { type Result = ReductionISSimpleToGrid; @@ -48,13 +48,9 @@ impl ReduceTo, i32>> let n = self.num_vertices(); let edges = self.edges(); let result = ksg::map_unweighted(n, &edges); - let weights: Vec = result - .grid_graph - .nodes() - .iter() - .map(|node| node.weight) - .collect(); - let target = MaximumIndependentSet::from_graph(result.grid_graph.clone(), weights); + let weights = result.node_weights.clone(); + let grid = result.to_kings_subgraph(); + let target = MaximumIndependentSet::from_graph(grid, weights); ReductionISSimpleToGrid { target, mapping_result: result, @@ -62,16 +58,16 @@ impl ReduceTo, i32>> } } -/// Result of reducing MIS on UnitDiskGraph to MIS on GridGraph. +/// Result of reducing MIS on UnitDiskGraph to MIS on KingsSubgraph. #[derive(Debug, Clone)] pub struct ReductionISUnitDiskToGrid { - target: MaximumIndependentSet, i32>, + target: MaximumIndependentSet, mapping_result: ksg::MappingResult, } impl ReductionResult for ReductionISUnitDiskToGrid { type Source = MaximumIndependentSet; - type Target = MaximumIndependentSet, i32>; + type Target = MaximumIndependentSet; fn target_problem(&self) -> &Self::Target { &self.target @@ -90,7 +86,7 @@ impl ReductionResult for ReductionISUnitDiskToGrid { ]) } )] -impl ReduceTo, i32>> +impl ReduceTo> for MaximumIndependentSet { type Result = ReductionISUnitDiskToGrid; @@ -99,13 +95,9 @@ impl ReduceTo, i32>> let n = self.num_vertices(); let edges = self.edges(); let result = ksg::map_unweighted(n, &edges); - let weights: Vec = result - .grid_graph - .nodes() - .iter() - .map(|node| node.weight) - .collect(); - let target = MaximumIndependentSet::from_graph(result.grid_graph.clone(), weights); + let weights = result.node_weights.clone(); + let grid = result.to_kings_subgraph(); + let target = MaximumIndependentSet::from_graph(grid, weights); ReductionISUnitDiskToGrid { target, mapping_result: result, diff --git a/src/rules/maximumindependentset_maximumsetpacking.rs b/src/rules/maximumindependentset_maximumsetpacking.rs index 92527167..13d79bfe 100644 --- a/src/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/rules/maximumindependentset_maximumsetpacking.rs @@ -21,7 +21,7 @@ pub struct ReductionISToSP { impl ReductionResult for ReductionISToSP where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Source = MaximumIndependentSet; type Target = MaximumSetPacking; @@ -72,7 +72,7 @@ pub struct ReductionSPToIS { impl ReductionResult for ReductionSPToIS where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Source = MaximumSetPacking; type Target = MaximumIndependentSet; diff --git a/src/rules/maximumindependentset_triangular.rs b/src/rules/maximumindependentset_triangular.rs index a779b170..acf1a2b7 100644 --- a/src/rules/maximumindependentset_triangular.rs +++ b/src/rules/maximumindependentset_triangular.rs @@ -1,4 +1,4 @@ -//! Reduction from MaximumIndependentSet on SimpleGraph to Triangular lattice +//! Reduction from MaximumIndependentSet on SimpleGraph to TriangularSubgraph //! using the weighted triangular unit disk mapping. //! //! Maps an arbitrary graph's MIS problem to an equivalent weighted MIS on a @@ -11,18 +11,18 @@ use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::rules::unitdiskmapping::ksg; use crate::rules::unitdiskmapping::triangular; -use crate::topology::{SimpleGraph, Triangular}; +use crate::topology::{SimpleGraph, TriangularSubgraph}; -/// Result of reducing MIS on SimpleGraph to MIS on Triangular. +/// Result of reducing MIS on SimpleGraph to MIS on TriangularSubgraph. #[derive(Debug, Clone)] pub struct ReductionISSimpleToTriangular { - target: MaximumIndependentSet, + target: MaximumIndependentSet, mapping_result: ksg::MappingResult, } impl ReductionResult for ReductionISSimpleToTriangular { type Source = MaximumIndependentSet; - type Target = MaximumIndependentSet; + type Target = MaximumIndependentSet; fn target_problem(&self) -> &Self::Target { &self.target @@ -41,20 +41,17 @@ impl ReductionResult for ReductionISSimpleToTriangular { ]) } )] -impl ReduceTo> for MaximumIndependentSet { +impl ReduceTo> + for MaximumIndependentSet +{ type Result = ReductionISSimpleToTriangular; fn reduce_to(&self) -> Self::Result { let n = self.num_vertices(); let edges = self.edges(); let result = triangular::map_weighted(n, &edges); - let weights: Vec = result - .grid_graph - .nodes() - .iter() - .map(|node| node.weight) - .collect(); - let grid = Triangular::new(result.grid_graph.clone()); + let weights = result.node_weights.clone(); + let grid = result.to_triangular_subgraph(); let target = MaximumIndependentSet::from_graph(grid, weights); ReductionISSimpleToTriangular { target, diff --git a/src/rules/maximummatching_maximumsetpacking.rs b/src/rules/maximummatching_maximumsetpacking.rs index f1c74890..a9092b31 100644 --- a/src/rules/maximummatching_maximumsetpacking.rs +++ b/src/rules/maximummatching_maximumsetpacking.rs @@ -21,8 +21,8 @@ pub struct ReductionMatchingToSP { impl ReductionResult for ReductionMatchingToSP where - G: Graph, - W: WeightElement, + G: Graph + crate::variant::VariantParam, + W: WeightElement + crate::variant::VariantParam, { type Source = MaximumMatching; type Target = MaximumSetPacking; diff --git a/src/rules/minimumvertexcover_maximumindependentset.rs b/src/rules/minimumvertexcover_maximumindependentset.rs index c8367c28..baf706e5 100644 --- a/src/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/rules/minimumvertexcover_maximumindependentset.rs @@ -18,7 +18,7 @@ pub struct ReductionISToVC { impl ReductionResult for ReductionISToVC where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Source = MaximumIndependentSet; type Target = MinimumVertexCover; @@ -63,7 +63,7 @@ pub struct ReductionVCToIS { impl ReductionResult for ReductionVCToIS where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Source = MinimumVertexCover; type Target = MaximumIndependentSet; diff --git a/src/rules/minimumvertexcover_minimumsetcovering.rs b/src/rules/minimumvertexcover_minimumsetcovering.rs index b8b707e8..0e6b82e0 100644 --- a/src/rules/minimumvertexcover_minimumsetcovering.rs +++ b/src/rules/minimumvertexcover_minimumsetcovering.rs @@ -20,7 +20,7 @@ pub struct ReductionVCToSC { impl ReductionResult for ReductionVCToSC where - W: WeightElement, + W: WeightElement + crate::variant::VariantParam, { type Source = MinimumVertexCover; type Target = MinimumSetCovering; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 9bf45d5d..cad26e5b 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -63,7 +63,8 @@ pub use circuit_spinglass::{ pub use coloring_qubo::ReductionKColoringToQUBO; pub use factoring_circuit::ReductionFactoringToCircuit; pub use graph::{ - EdgeJson, NodeJson, ReductionEdge, ReductionGraph, ReductionGraphJson, ReductionPath, + EdgeJson, EdgeKind, NodeJson, ReductionEdge, ReductionGraph, ReductionGraphJson, ReductionPath, + ReductionStep, ResolvedPath, }; pub use ksatisfiability_qubo::{Reduction3SATToQUBO, ReductionKSatToQUBO}; pub use maximumindependentset_gridgraph::{ReductionISSimpleToGrid, ReductionISUnitDiskToGrid}; @@ -100,13 +101,13 @@ pub use traits::{ReduceTo, ReductionAutoCast, ReductionResult}; macro_rules! impl_natural_reduction { ($Problem:ident, $SubGraph:ty, $SuperGraph:ty, $Weight:ty) => { #[reduction( - overhead = { - $crate::rules::registry::ReductionOverhead::new(vec![ - ("num_vertices", $crate::poly!(num_vertices)), - ("num_edges", $crate::poly!(num_edges)), - ]) - } - )] + overhead = { + $crate::rules::registry::ReductionOverhead::new(vec![ + ("num_vertices", $crate::poly!(num_vertices)), + ("num_edges", $crate::poly!(num_edges)), + ]) + } + )] impl $crate::rules::ReduceTo<$Problem<$SuperGraph, $Weight>> for $Problem<$SubGraph, $Weight> { diff --git a/src/rules/natural.rs b/src/rules/natural.rs index f617fdf9..ffae3643 100644 --- a/src/rules/natural.rs +++ b/src/rules/natural.rs @@ -1,19 +1,12 @@ //! Natural-edge reductions via graph subtype relaxation. //! -//! These reductions are trivial: a problem on a specific graph type -//! (e.g., `Triangular`) can always be solved as the same problem on a -//! more general graph type (e.g., `SimpleGraph`), since the specific -//! graph *is* a general graph. The solution mapping is identity. +//! Natural reductions (e.g., a problem on `Triangular` solved as `SimpleGraph`) +//! are handled automatically by [`ReductionGraph::resolve_path`], which inserts +//! `NaturalCast` steps based on the registered variant subtype hierarchies. //! -//! Each reduction is generated by [`impl_natural_reduction!`]. +//! No explicit `ReduceTo` impls are needed for natural edges — the resolver +//! computes them from `VariantTypeEntry` registrations. -use crate::impl_natural_reduction; -use crate::models::graph::MaximumIndependentSet; -use crate::reduction; -use crate::topology::{SimpleGraph, Triangular}; - -impl_natural_reduction!(MaximumIndependentSet, Triangular, SimpleGraph, i32); - -#[cfg(all(test, feature = "ilp"))] +#[cfg(test)] #[path = "../unit_tests/rules/natural.rs"] mod tests; diff --git a/src/rules/registry.rs b/src/rules/registry.rs index 532b6cbe..b9ac0598 100644 --- a/src/rules/registry.rs +++ b/src/rules/registry.rs @@ -4,7 +4,7 @@ use crate::polynomial::Polynomial; use crate::types::ProblemSize; /// Overhead specification for a reduction. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, serde::Serialize)] pub struct ReductionOverhead { /// Output size as polynomials of input size variables. /// Each entry is (output_field_name, polynomial). diff --git a/src/rules/sat_coloring.rs b/src/rules/sat_coloring.rs index e7fdfd78..e2bfaff5 100644 --- a/src/rules/sat_coloring.rs +++ b/src/rules/sat_coloring.rs @@ -16,6 +16,7 @@ use crate::rules::registry::ReductionOverhead; use crate::rules::sat_maximumindependentset::BoolVar; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::SimpleGraph; +use crate::variant::K3; use std::collections::HashMap; /// Helper struct for constructing the graph for the SAT to 3-Coloring reduction. @@ -199,8 +200,8 @@ impl SATColoringConstructor { } /// Build the final KColoring problem. - fn build_coloring(&self) -> KColoring<3, SimpleGraph> { - KColoring::<3, SimpleGraph>::new(self.num_vertices, self.edges.clone()) + fn build_coloring(&self) -> KColoring { + KColoring::::new(self.num_vertices, self.edges.clone()) } } @@ -213,7 +214,7 @@ impl SATColoringConstructor { #[derive(Debug, Clone)] pub struct ReductionSATToColoring { /// The target KColoring problem. - target: KColoring<3, SimpleGraph>, + target: KColoring, /// Mapping from variable index (0-indexed) to positive literal vertex index. pos_vertices: Vec, /// Mapping from variable index (0-indexed) to negative literal vertex index. @@ -226,7 +227,7 @@ pub struct ReductionSATToColoring { impl ReductionResult for ReductionSATToColoring { type Source = Satisfiability; - type Target = KColoring<3, SimpleGraph>; + type Target = KColoring; fn target_problem(&self) -> &Self::Target { &self.target @@ -304,7 +305,7 @@ impl ReductionSATToColoring { ]) } )] -impl ReduceTo> for Satisfiability { +impl ReduceTo> 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 687134a6..cb7e87c3 100644 --- a/src/rules/sat_ksat.rs +++ b/src/rules/sat_ksat.rs @@ -11,20 +11,21 @@ use crate::poly; use crate::reduction; use crate::rules::registry::ReductionOverhead; use crate::rules::traits::{ReduceTo, ReductionResult}; +use crate::variant::{KValue, K2, K3, KN}; /// Result of reducing general SAT to K-SAT. /// /// This reduction transforms a SAT formula into an equisatisfiable K-SAT formula /// by introducing ancilla (auxiliary) variables. #[derive(Debug, Clone)] -pub struct ReductionSATToKSAT { +pub struct ReductionSATToKSAT { /// Number of original variables in the source problem. source_num_vars: usize, /// The target K-SAT problem. target: KSatisfiability, } -impl ReductionResult for ReductionSATToKSAT { +impl ReductionResult for ReductionSATToKSAT { type Source = Satisfiability; type Target = KSatisfiability; @@ -108,18 +109,17 @@ fn add_clause_to_ksat( /// Implementation of SAT -> K-SAT reduction. /// /// Note: We implement this for specific K values rather than generic K -/// because Rust's type system requires concrete implementations for -/// the `ReduceTo` trait pattern used in this crate. +/// because the `#[reduction]` proc macro requires concrete types. macro_rules! impl_sat_to_ksat { - ($k:expr) => { + ($ktype:ty, $k:expr) => { #[reduction(overhead = { ReductionOverhead::new(vec![ ("num_clauses", poly!(num_clauses) + poly!(num_literals)), ("num_vars", poly!(num_vars) + poly!(num_literals)), ]) })] - impl ReduceTo> for Satisfiability { - type Result = ReductionSATToKSAT<$k>; + impl ReduceTo> for Satisfiability { + type Result = ReductionSATToKSAT<$ktype>; fn reduce_to(&self) -> Self::Result { let source_num_vars = self.num_vars(); @@ -133,7 +133,7 @@ macro_rules! impl_sat_to_ksat { // Calculate total number of variables (original + ancillas) let total_vars = (next_var - 1) as usize; - let target = KSatisfiability::<$k>::new(total_vars, result_clauses); + let target = KSatisfiability::<$ktype>::new(total_vars, result_clauses); ReductionSATToKSAT { source_num_vars, @@ -145,18 +145,19 @@ macro_rules! impl_sat_to_ksat { } // Implement for K=3 (the canonical NP-complete case) -impl_sat_to_ksat!(3); +impl_sat_to_ksat!(K3, 3); /// Result of reducing K-SAT to general SAT. /// /// This is a trivial embedding since K-SAT is a special case of SAT. #[derive(Debug, Clone)] -pub struct ReductionKSATToSAT { +pub struct ReductionKSATToSAT { /// The target SAT problem. target: Satisfiability, + _phantom: std::marker::PhantomData, } -impl ReductionResult for ReductionKSATToSAT { +impl ReductionResult for ReductionKSATToSAT { type Source = KSatisfiability; type Target = Satisfiability; @@ -170,23 +171,43 @@ impl ReductionResult for ReductionKSATToSAT { } } -#[reduction(overhead = { - ReductionOverhead::new(vec![ - ("num_clauses", poly!(num_clauses)), - ("num_vars", poly!(num_vars)), - ]) -})] -impl ReduceTo for KSatisfiability { - type Result = ReductionKSATToSAT; +/// Helper function for KSAT -> SAT reduction logic (generic over K). +fn reduce_ksat_to_sat(ksat: &KSatisfiability) -> ReductionKSATToSAT { + let clauses = ksat.clauses().to_vec(); + let target = Satisfiability::new(ksat.num_vars(), clauses); - fn reduce_to(&self) -> Self::Result { - let clauses = self.clauses().to_vec(); - let target = Satisfiability::new(self.num_vars(), clauses); - - ReductionKSATToSAT { target } + ReductionKSATToSAT { + target, + _phantom: std::marker::PhantomData, } } +/// Macro for concrete KSAT -> SAT reduction impls. +/// The `#[reduction]` macro requires concrete types. +macro_rules! impl_ksat_to_sat { + ($ktype:ty) => { + #[reduction(overhead = { + ReductionOverhead::new(vec![ + ("num_clauses", poly!(num_clauses)), + ("num_vars", poly!(num_vars)), + ]) + })] + impl ReduceTo for KSatisfiability<$ktype> { + type Result = ReductionKSATToSAT<$ktype>; + + fn reduce_to(&self) -> Self::Result { + reduce_ksat_to_sat(self) + } + } + }; +} + +// Register KN for the reduction graph (covers all K values as the generic entry) +impl_ksat_to_sat!(KN); +// Register K3 and K2 as concrete entries (used directly in tests and reductions) +impl_ksat_to_sat!(K3); +impl_ksat_to_sat!(K2); + #[cfg(test)] #[path = "../unit_tests/rules/sat_ksat.rs"] mod tests; diff --git a/src/rules/spinglass_maxcut.rs b/src/rules/spinglass_maxcut.rs index 8c314fe6..4d972ca6 100644 --- a/src/rules/spinglass_maxcut.rs +++ b/src/rules/spinglass_maxcut.rs @@ -22,6 +22,7 @@ pub struct ReductionMaxCutToSG { impl ReductionResult for ReductionMaxCutToSG where W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero @@ -99,6 +100,7 @@ pub struct ReductionSGToMaxCut { impl ReductionResult for ReductionSGToMaxCut where W: WeightElement + + crate::variant::VariantParam + PartialOrd + num_traits::Num + num_traits::Zero diff --git a/src/rules/unitdiskmapping/ksg/mapping.rs b/src/rules/unitdiskmapping/ksg/mapping.rs index 4c05d386..5a5bd4f8 100644 --- a/src/rules/unitdiskmapping/ksg/mapping.rs +++ b/src/rules/unitdiskmapping/ksg/mapping.rs @@ -17,19 +17,31 @@ use super::gadgets_weighted::{ weighted_tape_entry_mis_overhead, WeightedKsgPattern, WeightedKsgTapeEntry, }; use super::{PADDING, SPACING}; -use crate::topology::{GridGraph, GridNode, GridType}; +use crate::topology::{Graph, KingsSubgraph, TriangularSubgraph}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; -/// Unit radius for KSG square lattice grid graphs. -const KSG_UNIT_RADIUS: f64 = 1.5; +/// The kind of grid lattice used in a mapping result. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum GridKind { + /// Square lattice (King's SubGraph connectivity, radius 1.5). + Kings, + /// Triangular lattice (radius 1.1). + Triangular, +} -/// Result of mapping a graph to a KSG grid graph. +/// Result of mapping a graph to a grid graph. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MappingResult { - /// The resulting grid graph. - pub grid_graph: GridGraph, + /// Integer grid positions (row, col) for each node. + pub positions: Vec<(i32, i32)>, + /// Weight of each node. + pub node_weights: Vec, + /// Grid dimensions (rows, cols). + pub grid_dimensions: (usize, usize), + /// The kind of grid lattice. + pub kind: GridKind, /// Copy lines used in the mapping. pub lines: Vec, /// Padding used. @@ -46,16 +58,27 @@ pub struct MappingResult { } impl MappingResult { - /// Get the grid graph size. - pub fn grid_size(&self) -> (usize, usize) { - self.grid_graph.size() - } - /// Get the number of vertices in the original graph. pub fn num_original_vertices(&self) -> usize { self.lines.len() } + /// Compute edges based on grid kind. + pub fn edges(&self) -> Vec<(usize, usize)> { + match self.kind { + GridKind::Kings => self.to_kings_subgraph().edges(), + GridKind::Triangular => self.to_triangular_subgraph().edges(), + } + } + + /// Compute the number of edges based on grid kind. + pub fn num_edges(&self) -> usize { + match self.kind { + GridKind::Kings => self.to_kings_subgraph().num_edges(), + GridKind::Triangular => self.to_triangular_subgraph().num_edges(), + } + } + /// Print a configuration on the grid, highlighting selected nodes. /// /// Characters: @@ -68,12 +91,12 @@ impl MappingResult { /// Format a 2D configuration as a string. pub fn format_config(&self, config: &[Vec]) -> String { - let (rows, cols) = self.grid_graph.size(); + let (rows, cols) = self.grid_dimensions; // Build position to node index map let mut pos_to_node: HashMap<(i32, i32), usize> = HashMap::new(); - for (idx, node) in self.grid_graph.nodes().iter().enumerate() { - pos_to_node.insert((node.row, node.col), idx); + for (idx, &(r, c)) in self.positions.iter().enumerate() { + pos_to_node.insert((r, c), idx); } let mut lines = Vec::new(); @@ -116,7 +139,70 @@ impl MappingResult { /// Format a flat configuration vector as a string. pub fn format_config_flat(&self, config: &[usize]) -> String { - self.grid_graph.format_with_config(Some(config), false) + self.format_grid_with_config(Some(config)) + } + + /// Create a [`KingsSubgraph`] from this mapping result, extracting positions + /// and discarding weights. + pub fn to_kings_subgraph(&self) -> KingsSubgraph { + KingsSubgraph::new(self.positions.clone()) + } + + /// Create a [`TriangularSubgraph`] from this mapping result, extracting positions + /// and discarding weights. + pub fn to_triangular_subgraph(&self) -> TriangularSubgraph { + TriangularSubgraph::new(self.positions.clone()) + } + + /// Format the grid, optionally with a configuration overlay. + /// + /// Without config: shows weight values (single-char) or `●` for multi-char weights. + /// With config: shows `●` for selected nodes, `○` for unselected. + /// Empty cells show `⋅`. + fn format_grid_with_config(&self, config: Option<&[usize]>) -> String { + if self.positions.is_empty() { + return String::from("(empty grid graph)"); + } + + let (rows, cols) = self.grid_dimensions; + + let mut pos_to_idx: HashMap<(i32, i32), usize> = HashMap::new(); + for (idx, &(r, c)) in self.positions.iter().enumerate() { + pos_to_idx.insert((r, c), idx); + } + + let mut lines = Vec::new(); + + for r in 0..rows as i32 { + let mut line = String::new(); + for c in 0..cols as i32 { + let s = if let Some(&idx) = pos_to_idx.get(&(r, c)) { + if let Some(cfg) = config { + if cfg.get(idx).copied().unwrap_or(0) > 0 { + "●".to_string() + } else { + "○".to_string() + } + } else { + let w = self.node_weights[idx]; + let ws = format!("{}", w); + if ws.len() == 1 { + ws + } else { + "●".to_string() + } + } + } else { + "⋅".to_string() + }; + line.push_str(&s); + line.push(' '); + } + line.pop(); + lines.push(line); + } + + lines.join("\n") } } @@ -135,12 +221,12 @@ impl MappingResult { /// A vector where `result[v]` is 1 if vertex `v` is selected, 0 otherwise. pub fn map_config_back(&self, grid_config: &[usize]) -> Vec { // Step 1: Convert flat config to 2D matrix - let (rows, cols) = self.grid_graph.size(); + let (rows, cols) = self.grid_dimensions; let mut config_2d = vec![vec![0usize; cols]; rows]; - for (idx, node) in self.grid_graph.nodes().iter().enumerate() { - let row = node.row as usize; - let col = node.col as usize; + for (idx, &(row, col)) in self.positions.iter().enumerate() { + let row = row as usize; + let col = col as usize; if row < rows && col < cols { config_2d[row][col] = grid_config.get(idx).copied().unwrap_or(0); } @@ -163,8 +249,8 @@ impl MappingResult { pub fn map_config_back_via_centers(&self, grid_config: &[usize]) -> Vec { // Build a position to node index map let mut pos_to_idx: HashMap<(usize, usize), usize> = HashMap::new(); - for (idx, node) in self.grid_graph.nodes().iter().enumerate() { - if let (Ok(row), Ok(col)) = (usize::try_from(node.row), usize::try_from(node.col)) { + for (idx, &(row, col)) in self.positions.iter().enumerate() { + if let (Ok(row), Ok(col)) = (usize::try_from(row), usize::try_from(col)) { pos_to_idx.insert((row, col), idx); } } @@ -189,12 +275,12 @@ impl MappingResult { /// Map a configuration back from grid to original graph (weighted version). pub fn map_config_back(&self, grid_config: &[usize]) -> Vec { // Step 1: Convert flat config to 2D matrix - let (rows, cols) = self.grid_graph.size(); + let (rows, cols) = self.grid_dimensions; let mut config_2d = vec![vec![0usize; cols]; rows]; - for (idx, node) in self.grid_graph.nodes().iter().enumerate() { - let row = node.row as usize; - let col = node.col as usize; + for (idx, &(row, col)) in self.positions.iter().enumerate() { + let row = row as usize; + let col = col as usize; if row < rows && col < cols { config_2d[row][col] = grid_config.get(idx).copied().unwrap_or(0); } @@ -216,7 +302,7 @@ impl MappingResult { impl fmt::Display for MappingResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.grid_graph) + write!(f, "{}", self.format_grid_with_config(None)) } } @@ -507,21 +593,22 @@ pub fn map_unweighted_with_order( let gadget_overhead: i32 = tape.iter().map(tape_entry_mis_overhead).sum(); let mis_overhead = copyline_overhead + gadget_overhead; - // Convert to GridGraph - let nodes: Vec> = grid + // Extract positions and weights from occupied cells + let (positions, node_weights): (Vec<(i32, i32)>, Vec) = grid .occupied_coords() .into_iter() .filter_map(|(row, col)| { grid.get(row, col) - .map(|cell| GridNode::new(row as i32, col as i32, cell.weight())) + .map(|cell| ((row as i32, col as i32), cell.weight())) }) - .filter(|n| n.weight > 0) - .collect(); - - let grid_graph = GridGraph::new(GridType::Square, grid.size(), nodes, KSG_UNIT_RADIUS); + .filter(|&(_, w)| w > 0) + .unzip(); MappingResult { - grid_graph, + positions, + node_weights, + grid_dimensions: grid.size(), + kind: GridKind::Kings, lines: copylines, padding: PADDING, spacing: SPACING, @@ -598,21 +685,22 @@ pub fn map_weighted_with_order( let gadget_overhead: i32 = tape.iter().map(weighted_tape_entry_mis_overhead).sum(); let mis_overhead = copyline_overhead + gadget_overhead; - // Convert to GridGraph with weights - let nodes: Vec> = grid + // Extract positions and weights from occupied cells + let (positions, node_weights): (Vec<(i32, i32)>, Vec) = grid .occupied_coords() .into_iter() .filter_map(|(row, col)| { grid.get(row, col) - .map(|cell| GridNode::new(row as i32, col as i32, cell.weight())) + .map(|cell| ((row as i32, col as i32), cell.weight())) }) - .filter(|n| n.weight > 0) - .collect(); - - let grid_graph = GridGraph::new(GridType::Square, grid.size(), nodes, KSG_UNIT_RADIUS); + .filter(|&(_, w)| w > 0) + .unzip(); MappingResult { - grid_graph, + positions, + node_weights, + grid_dimensions: grid.size(), + kind: GridKind::Kings, lines: copylines, padding: PADDING, spacing: SPACING, diff --git a/src/rules/unitdiskmapping/ksg/mod.rs b/src/rules/unitdiskmapping/ksg/mod.rs index 8af9c7d7..85206453 100644 --- a/src/rules/unitdiskmapping/ksg/mod.rs +++ b/src/rules/unitdiskmapping/ksg/mod.rs @@ -40,7 +40,7 @@ pub use gadgets_weighted::{ pub use mapping::{ embed_graph, map_config_copyback, map_unweighted, map_unweighted_with_method, map_unweighted_with_order, map_weighted, map_weighted_with_method, map_weighted_with_order, - trace_centers, unapply_gadgets, unapply_weighted_gadgets, MappingResult, + trace_centers, unapply_gadgets, unapply_weighted_gadgets, GridKind, MappingResult, }; /// Spacing between copy lines for KSG mapping. diff --git a/src/rules/unitdiskmapping/mod.rs b/src/rules/unitdiskmapping/mod.rs index cfd8fc4f..d807103b 100644 --- a/src/rules/unitdiskmapping/mod.rs +++ b/src/rules/unitdiskmapping/mod.rs @@ -41,65 +41,8 @@ pub use pathdecomposition::{pathwidth, Layout, PathDecompositionMethod}; pub use traits::{apply_gadget, pattern_matches, unapply_gadget, Pattern, PatternCell}; // Re-export commonly used items from submodules for convenience -pub use ksg::MappingResult; +pub use ksg::{GridKind, MappingResult}; -// ============================================================================ -// BACKWARD COMPATIBILITY EXPORTS (deprecated - use ksg:: and triangular:: instead) -// ============================================================================ - -// Old function names pointing to new locations -pub use ksg::embed_graph; -pub use ksg::map_unweighted as map_graph; -pub use ksg::map_unweighted_with_method as map_graph_with_method; -pub use ksg::map_unweighted_with_order as map_graph_with_order; -pub use ksg::{PADDING as SQUARE_PADDING, SPACING as SQUARE_SPACING}; - -pub use triangular::map_weighted as map_graph_triangular; -pub use triangular::map_weighted_with_method as map_graph_triangular_with_method; -pub use triangular::map_weighted_with_order as map_graph_triangular_with_order; -pub use triangular::{PADDING as TRIANGULAR_PADDING, SPACING as TRIANGULAR_SPACING}; - -// Old gadget names -pub use ksg::{ - KsgBranch as Branch, KsgBranchFix as BranchFix, KsgBranchFixB as BranchFixB, KsgCross as Cross, - KsgDanglingLeg as DanglingLeg, KsgEndTurn as EndTurn, KsgPattern as SquarePattern, - KsgReflectedGadget as ReflectedGadget, KsgRotatedGadget as RotatedGadget, KsgTCon as TCon, - KsgTapeEntry as TapeEntry, KsgTrivialTurn as TrivialTurn, KsgTurn as Turn, KsgWTurn as WTurn, - Mirror, -}; - -pub use triangular::{ - WeightedTriBranch as TriBranch, WeightedTriBranchFix as TriBranchFix, - WeightedTriBranchFixB as TriBranchFixB, WeightedTriCross as TriCross, - WeightedTriEndTurn as TriEndTurn, WeightedTriTConDown as TriTConDown, - WeightedTriTConLeft as TriTConLeft, WeightedTriTConUp as TriTConUp, - WeightedTriTapeEntry as TriangularTapeEntry, WeightedTriTrivialTurnLeft as TriTrivialTurnLeft, - WeightedTriTrivialTurnRight as TriTrivialTurnRight, WeightedTriTurn as TriTurn, - WeightedTriWTurn as TriWTurn, WeightedTriangularGadget as TriangularGadget, -}; - -// Additional exports for weighted mode utilities +// Re-exports from private modules (not accessible via ksg:: or triangular::) pub use copyline::{copyline_weighted_locations_triangular, mis_overhead_copyline_triangular}; -pub use triangular::weighted_ruleset as triangular_weighted_ruleset; pub use weighted::{map_weights, trace_centers, Weightable}; - -// KSG gadget application functions -pub use ksg::{ - apply_crossing_gadgets, apply_simplifier_gadgets, apply_weighted_crossing_gadgets, - apply_weighted_simplifier_gadgets, tape_entry_mis_overhead, weighted_tape_entry_mis_overhead, - WeightedKsgTapeEntry, -}; - -// KSG weighted gadget types for testing -pub use ksg::{ - WeightedKsgBranch, WeightedKsgBranchFix, WeightedKsgBranchFixB, WeightedKsgCross, - WeightedKsgDanglingLeg, WeightedKsgEndTurn, WeightedKsgPattern, WeightedKsgTCon, - WeightedKsgTrivialTurn, WeightedKsgTurn, WeightedKsgWTurn, -}; - -// Triangular gadget application functions -pub use triangular::{ - apply_crossing_gadgets as apply_triangular_crossing_gadgets, - apply_simplifier_gadgets as apply_triangular_simplifier_gadgets, - tape_entry_mis_overhead as triangular_tape_entry_mis_overhead, -}; diff --git a/src/rules/unitdiskmapping/triangular/mapping.rs b/src/rules/unitdiskmapping/triangular/mapping.rs index 3f421599..89217f9c 100644 --- a/src/rules/unitdiskmapping/triangular/mapping.rs +++ b/src/rules/unitdiskmapping/triangular/mapping.rs @@ -11,7 +11,7 @@ use super::super::pathdecomposition::{ pathwidth, vertex_order_from_layout, PathDecompositionMethod, }; use super::gadgets::{apply_crossing_gadgets, apply_simplifier_gadgets, tape_entry_mis_overhead}; -use crate::topology::{GridGraph, GridNode, GridType}; +use crate::rules::unitdiskmapping::ksg::mapping::GridKind; /// Spacing between copy lines on triangular lattice. pub const SPACING: usize = 6; @@ -19,9 +19,6 @@ pub const SPACING: usize = 6; /// Padding around the grid for triangular lattice. pub const PADDING: usize = 2; -/// Unit radius for triangular lattice adjacency. -const UNIT_RADIUS: f64 = 1.1; - /// Calculate crossing point for two copylines on triangular lattice. fn crossat( copylines: &[CopyLine], @@ -72,7 +69,7 @@ fn crossat( /// /// let edges = vec![(0, 1), (1, 2)]; /// let result = map_weighted(3, &edges); -/// assert!(result.grid_graph.num_vertices() > 0); +/// assert!(result.to_triangular_subgraph().num_vertices() > 0); /// ``` pub fn map_weighted(num_vertices: usize, edges: &[(usize, usize)]) -> MappingResult { map_weighted_with_method(num_vertices, edges, PathDecompositionMethod::Auto) @@ -205,35 +202,25 @@ pub fn map_weighted_with_order( }) .collect(); - // Extract doubled cells before converting to GridGraph + // Extract doubled cells before extracting positions let doubled_cells = grid.doubled_cells(); - // Convert to GridGraph with triangular type - let nodes: Vec> = grid + // Extract positions and weights from occupied cells + let (positions, node_weights): (Vec<(i32, i32)>, Vec) = grid .occupied_coords() .into_iter() .filter_map(|(row, col)| { grid.get(row, col) - .map(|cell| GridNode::new(row as i32, col as i32, cell.weight())) + .map(|cell| ((row as i32, col as i32), cell.weight())) }) - .filter(|n| n.weight > 0) - .collect(); - - // Use Triangular grid type to match Julia's TriangularGrid() - // Julia uses 1-indexed coords where odd cols get offset 0.5. - // Rust uses 0-indexed coords, so even cols (0,2,4...) correspond to Julia's odd cols (1,3,5...). - // Therefore, offset_even_cols=true gives the same offset pattern as Julia. - let grid_graph = GridGraph::new( - GridType::Triangular { - offset_even_cols: true, - }, - grid.size(), - nodes, - UNIT_RADIUS, - ); + .filter(|&(_, w)| w > 0) + .unzip(); MappingResult { - grid_graph, + positions, + node_weights, + grid_dimensions: grid.size(), + kind: GridKind::Triangular, lines: copylines, padding, spacing, diff --git a/src/rules/unitdiskmapping/triangular/mod.rs b/src/rules/unitdiskmapping/triangular/mod.rs index b126f396..06b94337 100644 --- a/src/rules/unitdiskmapping/triangular/mod.rs +++ b/src/rules/unitdiskmapping/triangular/mod.rs @@ -46,14 +46,11 @@ use super::grid::MappingGrid; use super::ksg::mapping::MappingResult; use super::ksg::KsgTapeEntry as TapeEntry; use super::pathdecomposition::{pathwidth, vertex_order_from_layout, PathDecompositionMethod}; -use crate::topology::{GridGraph, GridNode, GridType}; +use crate::rules::unitdiskmapping::ksg::mapping::GridKind; use serde::{Deserialize, Serialize}; pub const TRIANGULAR_SPACING: usize = 6; pub const TRIANGULAR_PADDING: usize = 2; -// Use radius 1.1 to match Julia's TRIANGULAR_UNIT_RADIUS -// For triangular lattice, physical positions use sqrt(3)/2 scaling for y -const TRIANGULAR_UNIT_RADIUS: f64 = 1.1; /// Tape entry recording a triangular gadget application. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -1588,35 +1585,25 @@ pub fn map_graph_triangular_with_order( }) .collect(); - // Extract doubled cells before converting to GridGraph + // Extract doubled cells before extracting positions let doubled_cells = grid.doubled_cells(); - // Convert to GridGraph with triangular type - let nodes: Vec> = grid + // Extract positions and weights from occupied cells + let (positions, node_weights): (Vec<(i32, i32)>, Vec) = grid .occupied_coords() .into_iter() .filter_map(|(row, col)| { grid.get(row, col) - .map(|cell| GridNode::new(row as i32, col as i32, cell.weight())) + .map(|cell| ((row as i32, col as i32), cell.weight())) }) - .filter(|n| n.weight > 0) - .collect(); - - // Use Triangular grid type to match Julia's TriangularGrid() - // Julia uses 1-indexed coords where odd cols get offset 0.5. - // Rust uses 0-indexed coords, so even cols (0,2,4...) correspond to Julia's odd cols (1,3,5...). - // Therefore, offset_even_cols=true gives the same offset pattern as Julia. - let grid_graph = GridGraph::new( - GridType::Triangular { - offset_even_cols: true, - }, - grid.size(), - nodes, - TRIANGULAR_UNIT_RADIUS, - ); + .filter(|&(_, w)| w > 0) + .unzip(); MappingResult { - grid_graph, + positions, + node_weights, + grid_dimensions: grid.size(), + kind: GridKind::Triangular, lines: copylines, padding, spacing, diff --git a/src/rules/unitdiskmapping/weighted.rs b/src/rules/unitdiskmapping/weighted.rs index 8683c134..5fb27985 100644 --- a/src/rules/unitdiskmapping/weighted.rs +++ b/src/rules/unitdiskmapping/weighted.rs @@ -458,10 +458,9 @@ pub fn map_weights(result: &MappingResult, source_weights: &[f64]) -> Vec { // Start with base weights from grid nodes let mut weights: Vec = result - .grid_graph - .nodes() + .node_weights .iter() - .map(|n| n.weight as f64) + .map(|&w| w as f64) .collect(); // Get center locations for each original vertex @@ -472,10 +471,9 @@ pub fn map_weights(result: &MappingResult, source_weights: &[f64]) -> Vec { let center = centers[vertex]; // Find the node index at this center location if let Some(idx) = result - .grid_graph - .nodes() + .positions .iter() - .position(|n| n.row as usize == center.0 && n.col as usize == center.1) + .position(|&(r, c)| r as usize == center.0 && c as usize == center.1) { weights[idx] += src_weight; } diff --git a/src/topology/graph.rs b/src/topology/graph.rs index fc8b8386..485f167f 100644 --- a/src/topology/graph.rs +++ b/src/topology/graph.rs @@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize}; /// } /// ``` pub trait Graph: Clone + Send + Sync + 'static { - /// The name of the graph type (e.g., "SimpleGraph", "GridGraph"). + /// The name of the graph type (e.g., "SimpleGraph", "KingsSubgraph"). const NAME: &'static str; /// Returns the number of vertices in the graph. @@ -278,6 +278,14 @@ impl PartialEq for SimpleGraph { impl Eq for SimpleGraph {} +use super::hypergraph::HyperGraph; +use crate::impl_variant_param; +impl_variant_param!(SimpleGraph, "graph", parent: HyperGraph, +cast: |g| { + let edges: Vec> = g.edges().into_iter().map(|(u, v)| vec![u, v]).collect(); + HyperGraph::new(g.num_vertices(), edges) +}); + #[cfg(test)] #[path = "../unit_tests/topology/graph.rs"] mod tests; diff --git a/src/topology/grid_graph.rs b/src/topology/grid_graph.rs deleted file mode 100644 index 498596a9..00000000 --- a/src/topology/grid_graph.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Grid Graph implementation. -//! -//! A grid graph is a weighted graph on a 2D integer lattice, where edges are -//! determined by distance (unit disk graph property). Supports both square -//! and triangular lattice geometries. - -use super::graph::Graph; -use serde::{Deserialize, Serialize}; -use std::fmt; - -/// The type of grid lattice. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum GridType { - /// Square lattice where physical position (row, col) = (row, col). - Square, - /// Triangular lattice where: - /// - y = col * (sqrt(3) / 2) - /// - x = row + offset, where offset is 0.5 for odd/even columns depending on `offset_even_cols` - Triangular { - /// If true, even columns are offset by 0.5; if false, odd columns are offset. - offset_even_cols: bool, - }, -} - -/// A node in a grid graph with integer coordinates and a weight. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct GridNode { - /// Row coordinate (integer). - pub row: i32, - /// Column coordinate (integer). - pub col: i32, - /// Weight of the node. - pub weight: W, -} - -impl GridNode { - /// Create a new grid node. - pub fn new(row: i32, col: i32, weight: W) -> Self { - Self { row, col, weight } - } -} - -/// A weighted graph on a 2D integer lattice. -/// -/// Edges are determined by distance: two nodes are connected if their -/// physical distance is at most the specified radius. -/// -/// # Example -/// -/// ``` -/// use problemreductions::topology::{Graph, GridGraph, GridNode, GridType}; -/// -/// let nodes = vec![ -/// GridNode::new(0, 0, 1), -/// GridNode::new(1, 0, 1), -/// GridNode::new(0, 1, 1), -/// ]; -/// let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 1.5); -/// assert_eq!(grid.num_vertices(), 3); -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct GridGraph { - /// The type of grid lattice. - grid_type: GridType, - /// The size of the grid as (rows, cols). - size: (usize, usize), - /// The nodes in the graph. - nodes: Vec>, - /// The radius threshold for edge creation. - radius: f64, - /// Precomputed edges as (node_index, node_index) pairs. - edges: Vec<(usize, usize)>, -} - -impl GridGraph { - /// Create a new grid graph. - /// - /// # Arguments - /// - /// * `grid_type` - The type of lattice (Square or Triangular) - /// * `size` - The size of the grid as (rows, cols) - /// * `nodes` - The nodes in the graph with their coordinates and weights - /// * `radius` - Maximum distance for an edge to exist - pub fn new( - grid_type: GridType, - size: (usize, usize), - nodes: Vec>, - radius: f64, - ) -> Self { - let n = nodes.len(); - let mut edges = Vec::new(); - - // Compute all edges based on physical distance - // Use strict < to match Julia's unitdisk_graph which uses: dist² < radius² - for i in 0..n { - for j in (i + 1)..n { - let pos_i = Self::physical_position_static(grid_type, nodes[i].row, nodes[i].col); - let pos_j = Self::physical_position_static(grid_type, nodes[j].row, nodes[j].col); - let dist = Self::distance(&pos_i, &pos_j); - if dist < radius { - edges.push((i, j)); - } - } - } - - Self { - grid_type, - size, - nodes, - radius, - edges, - } - } - - /// Get the grid type. - pub fn grid_type(&self) -> GridType { - self.grid_type - } - - /// Get the size of the grid as (rows, cols). - pub fn size(&self) -> (usize, usize) { - self.size - } - - /// Get the radius threshold. - pub fn radius(&self) -> f64 { - self.radius - } - - /// Get the nodes. - pub fn nodes(&self) -> &[GridNode] { - &self.nodes - } - - /// Get a node by index. - pub fn node(&self, index: usize) -> Option<&GridNode> { - self.nodes.get(index) - } - - /// Get the weight of a node by index. - pub fn weight(&self, index: usize) -> Option<&W> { - self.nodes.get(index).map(|n| &n.weight) - } - - /// Compute the physical position of a grid coordinate. - /// - /// For Square: (row, col) -> (row, col) - /// For Triangular: - /// - y = col * (sqrt(3) / 2) - /// - x = row + offset, where offset is 0.5 for odd/even columns - pub fn physical_position(&self, row: i32, col: i32) -> (f64, f64) { - Self::physical_position_static(self.grid_type, row, col) - } - - /// Static version of physical_position for use during construction. - #[allow(unknown_lints, clippy::manual_is_multiple_of)] // i32 doesn't support is_multiple_of yet - fn physical_position_static(grid_type: GridType, row: i32, col: i32) -> (f64, f64) { - match grid_type { - GridType::Square => (row as f64, col as f64), - GridType::Triangular { offset_even_cols } => { - let y = col as f64 * (3.0_f64.sqrt() / 2.0); - let offset = if offset_even_cols { - if col % 2 == 0 { - 0.5 - } else { - 0.0 - } - } else if col % 2 != 0 { - 0.5 - } else { - 0.0 - }; - let x = row as f64 + offset; - (x, y) - } - } - } - - /// Compute Euclidean distance between two points. - fn distance(p1: &(f64, f64), p2: &(f64, f64)) -> f64 { - let dx = p1.0 - p2.0; - let dy = p1.1 - p2.1; - (dx * dx + dy * dy).sqrt() - } - - /// Get all edges as a slice. - pub fn edges(&self) -> &[(usize, usize)] { - &self.edges - } - - /// Get the physical position of a node by index. - pub fn node_position(&self, index: usize) -> Option<(f64, f64)> { - self.nodes - .get(index) - .map(|n| self.physical_position(n.row, n.col)) - } -} - -impl Graph for GridGraph { - const NAME: &'static str = "GridGraph"; - - fn num_vertices(&self) -> usize { - self.nodes.len() - } - - fn num_edges(&self) -> usize { - self.edges.len() - } - - fn edges(&self) -> Vec<(usize, usize)> { - self.edges.clone() - } - - fn has_edge(&self, u: usize, v: usize) -> bool { - let (u, v) = if u < v { (u, v) } else { (v, u) }; - self.edges.contains(&(u, v)) - } - - fn neighbors(&self, v: usize) -> Vec { - self.edges - .iter() - .filter_map(|&(u1, u2)| { - if u1 == v { - Some(u2) - } else if u2 == v { - Some(u1) - } else { - None - } - }) - .collect() - } -} - -impl GridGraph { - /// Format the grid graph as a string matching Julia's UnitDiskMapping format. - /// - /// Characters (matching Julia exactly): - /// - `⋅` = empty cell - /// - `●` = node (or selected node when config provided) - /// - `○` = unselected node (when config provided) - /// - Each cell is followed by a space - /// - /// When show_weight is true, displays the weight as a number for single digits. - pub fn format_with_config(&self, config: Option<&[usize]>, show_weight: bool) -> String { - use std::collections::HashMap; - - if self.nodes.is_empty() { - return String::from("(empty grid graph)"); - } - - // Find grid bounds (use full size, not min/max of nodes) - let (rows, cols) = self.size; - - // Build position to node index map - let mut pos_to_idx: HashMap<(i32, i32), usize> = HashMap::new(); - for (idx, node) in self.nodes.iter().enumerate() { - pos_to_idx.insert((node.row, node.col), idx); - } - - let mut lines = Vec::new(); - - for r in 0..rows as i32 { - let mut line = String::new(); - for c in 0..cols as i32 { - let s = if let Some(&idx) = pos_to_idx.get(&(r, c)) { - if let Some(cfg) = config { - if cfg.get(idx).copied().unwrap_or(0) > 0 { - "●".to_string() // Selected node - } else { - "○".to_string() // Unselected node - } - } else if show_weight { - Self::weight_str(&self.nodes[idx].weight) - } else { - "●".to_string() - } - } else { - "⋅".to_string() - }; - line.push_str(&s); - line.push(' '); - } - // Remove trailing space - line.pop(); - lines.push(line); - } - - lines.join("\n") - } - - /// Get a string representation of a weight. - fn weight_str(weight: &W) -> String { - let s = format!("{}", weight); - if s.len() == 1 { - s - } else { - "●".to_string() - } - } - - /// Print a configuration on this grid graph. - /// - /// This is equivalent to Julia's `print_config(res, c)`. - pub fn print_config(&self, config: &[usize]) { - print!("{}", self.format_with_config(Some(config), false)); - } -} - -impl fmt::Display for GridGraph { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.format_with_config(None, true)) - } -} - -#[cfg(test)] -#[path = "../unit_tests/topology/grid_graph.rs"] -mod tests; diff --git a/src/topology/hypergraph.rs b/src/topology/hypergraph.rs index 88018a6a..03e9914c 100644 --- a/src/topology/hypergraph.rs +++ b/src/topology/hypergraph.rs @@ -146,6 +146,9 @@ impl HyperGraph { } } +use crate::impl_variant_param; +impl_variant_param!(HyperGraph, "graph"); + #[cfg(test)] #[path = "../unit_tests/topology/hypergraph.rs"] mod tests; diff --git a/src/topology/kings_subgraph.rs b/src/topology/kings_subgraph.rs new file mode 100644 index 00000000..49b55730 --- /dev/null +++ b/src/topology/kings_subgraph.rs @@ -0,0 +1,123 @@ +//! King's Subgraph — an unweighted unit disk graph on a square grid (king's move connectivity). +//! +//! This is a public graph type produced by the KSG unit disk mapping reduction. +//! It stores only integer grid positions; edges are computed on-the-fly from geometry. + +use super::graph::Graph; +use super::unit_disk_graph::UnitDiskGraph; +use serde::{Deserialize, Serialize}; + +/// A King's Subgraph — an unweighted unit disk graph on a square lattice. +/// +/// Vertices occupy integer grid positions with edges determined by distance +/// (king's move connectivity: adjacent horizontally, vertically, or diagonally). +/// This is a subtype of [`UnitDiskGraph`] in the variant hierarchy. +/// +/// Edges are computed on-the-fly: two positions are connected if their +/// Euclidean distance is strictly less than 1.5. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct KingsSubgraph { + /// Integer grid positions (row, col) for each vertex. + positions: Vec<(i32, i32)>, +} + +/// Fixed radius for king's move connectivity on integer grid. +const KINGS_RADIUS: f64 = 1.5; + +impl KingsSubgraph { + /// Create a KingsSubgraph from a list of integer positions. + pub fn new(positions: Vec<(i32, i32)>) -> Self { + Self { positions } + } + + /// Get the positions of all vertices. + pub fn positions(&self) -> &[(i32, i32)] { + &self.positions + } + + /// Get the number of positions (vertices). + pub fn num_positions(&self) -> usize { + self.positions.len() + } + + /// Compute Euclidean distance between two integer positions. + fn distance(p1: (i32, i32), p2: (i32, i32)) -> f64 { + let dx = (p1.0 - p2.0) as f64; + let dy = (p1.1 - p2.1) as f64; + (dx * dx + dy * dy).sqrt() + } +} + +impl Graph for KingsSubgraph { + const NAME: &'static str = "KingsSubgraph"; + + fn num_vertices(&self) -> usize { + self.positions.len() + } + + fn num_edges(&self) -> usize { + let n = self.positions.len(); + let mut count = 0; + for i in 0..n { + for j in (i + 1)..n { + if Self::distance(self.positions[i], self.positions[j]) < KINGS_RADIUS { + count += 1; + } + } + } + count + } + + fn edges(&self) -> Vec<(usize, usize)> { + let n = self.positions.len(); + let mut edges = Vec::new(); + for i in 0..n { + for j in (i + 1)..n { + if Self::distance(self.positions[i], self.positions[j]) < KINGS_RADIUS { + edges.push((i, j)); + } + } + } + edges + } + + fn has_edge(&self, u: usize, v: usize) -> bool { + if u >= self.positions.len() || v >= self.positions.len() || u == v { + return false; + } + Self::distance(self.positions[u], self.positions[v]) < KINGS_RADIUS + } + + fn neighbors(&self, v: usize) -> Vec { + if v >= self.positions.len() { + return Vec::new(); + } + (0..self.positions.len()) + .filter(|&u| u != v && Self::distance(self.positions[v], self.positions[u]) < KINGS_RADIUS) + .collect() + } +} + +impl crate::variant::VariantParam for KingsSubgraph { + const CATEGORY: &'static str = "graph"; + const VALUE: &'static str = "KingsSubgraph"; + const PARENT_VALUE: Option<&'static str> = Some("UnitDiskGraph"); +} +impl crate::variant::CastToParent for KingsSubgraph { + type Parent = UnitDiskGraph; + fn cast_to_parent(&self) -> UnitDiskGraph { + let positions: Vec<(f64, f64)> = self + .positions + .iter() + .map(|&(r, c)| (r as f64, c as f64)) + .collect(); + UnitDiskGraph::new(positions, KINGS_RADIUS) + } +} +inventory::submit! { + crate::variant::VariantTypeEntry { + category: "graph", + value: "KingsSubgraph", + parent: Some("UnitDiskGraph"), + } +} diff --git a/src/topology/mod.rs b/src/topology/mod.rs index bfde7523..7d1eaf63 100644 --- a/src/topology/mod.rs +++ b/src/topology/mod.rs @@ -23,15 +23,15 @@ //! ``` mod graph; -mod grid_graph; mod hypergraph; +mod kings_subgraph; pub mod small_graphs; -mod triangular; +mod triangular_subgraph; mod unit_disk_graph; pub use graph::{Graph, GraphCast, SimpleGraph}; -pub use grid_graph::{GridGraph, GridNode, GridType}; pub use hypergraph::HyperGraph; +pub use kings_subgraph::KingsSubgraph; pub use small_graphs::{available_graphs, smallgraph}; -pub use triangular::Triangular; +pub use triangular_subgraph::TriangularSubgraph; pub use unit_disk_graph::UnitDiskGraph; diff --git a/src/topology/triangular.rs b/src/topology/triangular.rs deleted file mode 100644 index f1d20ece..00000000 --- a/src/topology/triangular.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Triangular lattice graph — a weighted unit disk graph on a triangular grid. -//! -//! This is a newtype wrapper around [`GridGraph`] with triangular geometry, -//! exposed as a distinct graph type for the reduction system. - -use super::graph::Graph; -use super::grid_graph::GridGraph; -use serde::{Deserialize, Serialize}; - -/// A triangular lattice graph. -/// -/// Wraps a [`GridGraph`] that uses triangular lattice geometry. -/// This is a subtype of `UnitDiskGraph` — all triangular lattice graphs -/// are unit disk graphs (and therefore also simple graphs). -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Triangular(GridGraph); - -impl Triangular { - /// Create a new Triangular graph from a GridGraph. - pub fn new(grid_graph: GridGraph) -> Self { - Self(grid_graph) - } - - /// Get a reference to the inner GridGraph. - pub fn grid_graph(&self) -> &GridGraph { - &self.0 - } - - /// Get the nodes of the graph. - pub fn nodes(&self) -> &[super::grid_graph::GridNode] { - self.0.nodes() - } -} - -impl Graph for Triangular { - const NAME: &'static str = "Triangular"; - - fn num_vertices(&self) -> usize { - self.0.num_vertices() - } - - fn num_edges(&self) -> usize { - self.0.num_edges() - } - - fn edges(&self) -> Vec<(usize, usize)> { - Graph::edges(&self.0) - } - - fn has_edge(&self, u: usize, v: usize) -> bool { - self.0.has_edge(u, v) - } - - fn neighbors(&self, v: usize) -> Vec { - self.0.neighbors(v) - } -} diff --git a/src/topology/triangular_subgraph.rs b/src/topology/triangular_subgraph.rs new file mode 100644 index 00000000..0612893d --- /dev/null +++ b/src/topology/triangular_subgraph.rs @@ -0,0 +1,151 @@ +//! Triangular Subgraph — an unweighted unit disk graph on a triangular lattice. +//! +//! This is a public graph type produced by the triangular unit disk mapping reduction. +//! It stores only integer grid positions; edges are computed on-the-fly from geometry. + +use super::graph::Graph; +use super::unit_disk_graph::UnitDiskGraph; +use serde::{Deserialize, Serialize}; + +/// A Triangular Subgraph — an unweighted unit disk graph on a triangular lattice. +/// +/// Vertices occupy positions on a triangular grid with edges determined by distance. +/// This is a subtype of [`UnitDiskGraph`] in the variant hierarchy. +/// +/// Physical position for integer coordinates `(row, col)`: +/// - `x = row + 0.5` if col is even, else `x = row` +/// - `y = col * sqrt(3)/2` +/// +/// Edges are computed on-the-fly: two positions are connected if their +/// physical Euclidean distance is strictly less than 1.1. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TriangularSubgraph { + /// Integer grid positions (row, col) for each vertex. + positions: Vec<(i32, i32)>, +} + +/// Fixed radius for triangular lattice adjacency. +const TRIANGULAR_RADIUS: f64 = 1.1; + +impl TriangularSubgraph { + /// Create a TriangularSubgraph from a list of integer positions. + pub fn new(positions: Vec<(i32, i32)>) -> Self { + Self { positions } + } + + /// Get the positions of all vertices. + pub fn positions(&self) -> &[(i32, i32)] { + &self.positions + } + + /// Get the number of positions (vertices). + pub fn num_positions(&self) -> usize { + self.positions.len() + } + + /// Compute the physical position for a triangular lattice coordinate. + /// + /// Uses `offset_even_cols = true` convention: + /// - `x = row + 0.5` if col is even, else `x = row` + /// - `y = col * sqrt(3)/2` + #[allow(unknown_lints, clippy::manual_is_multiple_of)] + fn physical_position(row: i32, col: i32) -> (f64, f64) { + let y = col as f64 * (3.0_f64.sqrt() / 2.0); + let offset = if col % 2 == 0 { 0.5 } else { 0.0 }; + let x = row as f64 + offset; + (x, y) + } + + /// Compute Euclidean distance between two physical positions. + fn distance(p1: (f64, f64), p2: (f64, f64)) -> f64 { + let dx = p1.0 - p2.0; + let dy = p1.1 - p2.1; + (dx * dx + dy * dy).sqrt() + } +} + +impl Graph for TriangularSubgraph { + const NAME: &'static str = "TriangularSubgraph"; + + fn num_vertices(&self) -> usize { + self.positions.len() + } + + fn num_edges(&self) -> usize { + let n = self.positions.len(); + let mut count = 0; + for i in 0..n { + let pi = Self::physical_position(self.positions[i].0, self.positions[i].1); + for j in (i + 1)..n { + let pj = Self::physical_position(self.positions[j].0, self.positions[j].1); + if Self::distance(pi, pj) < TRIANGULAR_RADIUS { + count += 1; + } + } + } + count + } + + fn edges(&self) -> Vec<(usize, usize)> { + let n = self.positions.len(); + let mut edges = Vec::new(); + for i in 0..n { + let pi = Self::physical_position(self.positions[i].0, self.positions[i].1); + for j in (i + 1)..n { + let pj = Self::physical_position(self.positions[j].0, self.positions[j].1); + if Self::distance(pi, pj) < TRIANGULAR_RADIUS { + edges.push((i, j)); + } + } + } + edges + } + + fn has_edge(&self, u: usize, v: usize) -> bool { + if u >= self.positions.len() || v >= self.positions.len() || u == v { + return false; + } + let pu = Self::physical_position(self.positions[u].0, self.positions[u].1); + let pv = Self::physical_position(self.positions[v].0, self.positions[v].1); + Self::distance(pu, pv) < TRIANGULAR_RADIUS + } + + fn neighbors(&self, v: usize) -> Vec { + if v >= self.positions.len() { + return Vec::new(); + } + let pv = Self::physical_position(self.positions[v].0, self.positions[v].1); + (0..self.positions.len()) + .filter(|&u| { + u != v && { + let pu = Self::physical_position(self.positions[u].0, self.positions[u].1); + Self::distance(pv, pu) < TRIANGULAR_RADIUS + } + }) + .collect() + } +} + +impl crate::variant::VariantParam for TriangularSubgraph { + const CATEGORY: &'static str = "graph"; + const VALUE: &'static str = "TriangularSubgraph"; + const PARENT_VALUE: Option<&'static str> = Some("UnitDiskGraph"); +} +impl crate::variant::CastToParent for TriangularSubgraph { + type Parent = UnitDiskGraph; + fn cast_to_parent(&self) -> UnitDiskGraph { + let positions: Vec<(f64, f64)> = self + .positions + .iter() + .map(|&(r, c)| Self::physical_position(r, c)) + .collect(); + UnitDiskGraph::new(positions, TRIANGULAR_RADIUS) + } +} +inventory::submit! { + crate::variant::VariantTypeEntry { + category: "graph", + value: "TriangularSubgraph", + parent: Some("UnitDiskGraph"), + } +} diff --git a/src/topology/unit_disk_graph.rs b/src/topology/unit_disk_graph.rs index 82ceaf2b..0c8a473f 100644 --- a/src/topology/unit_disk_graph.rs +++ b/src/topology/unit_disk_graph.rs @@ -228,6 +228,11 @@ impl Graph for UnitDiskGraph { } } +use super::graph::SimpleGraph; +use crate::impl_variant_param; +impl_variant_param!(UnitDiskGraph, "graph", parent: SimpleGraph, + cast: |g| SimpleGraph::new(g.num_vertices(), Graph::edges(g))); + #[cfg(test)] #[path = "../unit_tests/topology/unit_disk_graph.rs"] mod tests; diff --git a/src/types.rs b/src/types.rs index d962d112..99236ca3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -217,6 +217,12 @@ impl fmt::Display for ProblemSize { } } +use crate::impl_variant_param; + +impl_variant_param!(f64, "weight"); +impl_variant_param!(i32, "weight", parent: f64, cast: |w| *w as f64); +impl_variant_param!(One, "weight", parent: i32, cast: |_| 1i32); + #[cfg(test)] #[path = "unit_tests/types.rs"] mod tests; diff --git a/src/unit_tests/export.rs b/src/unit_tests/export.rs index dbc19b3d..25c43c63 100644 --- a/src/unit_tests/export.rs +++ b/src/unit_tests/export.rs @@ -96,28 +96,24 @@ fn test_variant_to_map_multiple() { #[test] fn test_lookup_overhead_known_reduction() { // IS -> VC is a known registered reduction - let result = lookup_overhead("MaximumIndependentSet", "MinimumVertexCover"); + let source_variant = variant_to_map(vec![("graph", "SimpleGraph"), ("weight", "i32")]); + let target_variant = variant_to_map(vec![("graph", "SimpleGraph"), ("weight", "i32")]); + let result = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "MinimumVertexCover", + &target_variant, + ); assert!(result.is_some()); } #[test] fn test_lookup_overhead_unknown_reduction() { - let result = lookup_overhead("NonExistent", "AlsoNonExistent"); + let empty = variant_to_map(vec![]); + let result = lookup_overhead("NonExistent", &empty, "AlsoNonExistent", &empty); assert!(result.is_none()); } -#[test] -fn test_lookup_overhead_or_empty_known() { - let overhead = lookup_overhead_or_empty("MaximumIndependentSet", "MinimumVertexCover"); - assert!(!overhead.output_size.is_empty()); -} - -#[test] -fn test_lookup_overhead_or_empty_unknown() { - let overhead = lookup_overhead_or_empty("NonExistent", "AlsoNonExistent"); - assert!(overhead.output_size.is_empty()); -} - #[test] fn test_write_example_creates_files() { use std::fs; diff --git a/src/unit_tests/graph_models.rs b/src/unit_tests/graph_models.rs index 97d897e5..193b8f26 100644 --- a/src/unit_tests/graph_models.rs +++ b/src/unit_tests/graph_models.rs @@ -11,6 +11,7 @@ use crate::prelude::*; use crate::topology::SimpleGraph; use crate::traits::{OptimizationProblem, Problem}; use crate::types::{Direction, SolutionSize}; +use crate::variant::{K1, K2, K3, K4}; // ============================================================================= // Independent Set Tests @@ -384,7 +385,7 @@ mod kcoloring { #[test] fn test_creation() { - let problem = KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::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 +394,7 @@ mod kcoloring { #[test] fn test_evaluate_valid() { - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); // Valid: different colors on adjacent vertices - returns true assert!(problem.evaluate(&[0, 1, 0])); @@ -402,7 +403,7 @@ mod kcoloring { #[test] fn test_evaluate_invalid() { - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); // Invalid: adjacent vertices have same color assert!(!problem.evaluate(&[0, 0, 1])); // 0-1 conflict @@ -412,7 +413,7 @@ mod kcoloring { #[test] fn test_brute_force_path() { // Path graph can be 2-colored - let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -425,7 +426,7 @@ mod kcoloring { #[test] fn test_brute_force_triangle() { // Triangle needs 3 colors - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -441,7 +442,7 @@ mod kcoloring { #[test] fn test_triangle_2_colors_unsat() { // Triangle cannot be 2-colored - let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); // No satisfying assignments @@ -463,7 +464,7 @@ mod kcoloring { #[test] fn test_empty_graph() { - let problem = KColoring::<1, SimpleGraph>::new(3, vec![]); + let problem = KColoring::::new(3, vec![]); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -474,7 +475,7 @@ mod kcoloring { #[test] fn test_complete_graph_k4() { // K4 needs 4 colors - let problem = KColoring::<4, SimpleGraph>::new( + let problem = KColoring::::new( 4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], ); diff --git a/src/unit_tests/graph_types.rs b/src/unit_tests/graph_types.rs index c52e6462..a462665e 100644 --- a/src/unit_tests/graph_types.rs +++ b/src/unit_tests/graph_types.rs @@ -1,50 +1,5 @@ use super::*; - -#[test] -fn test_reflexive_subtype() { - fn assert_subtype, B: GraphMarker>() {} - - // Every type is a subtype of itself - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); -} - -#[test] -fn test_subtype_entries_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - - // Should have at least 10 entries - assert!(entries.len() >= 10); - - // Check specific relationships - assert!(entries - .iter() - .any(|e| e.subtype == "UnitDiskGraph" && e.supertype == "SimpleGraph")); - assert!(entries - .iter() - .any(|e| e.subtype == "PlanarGraph" && e.supertype == "SimpleGraph")); -} - -#[test] -fn test_declared_subtypes() { - fn assert_subtype, B: GraphMarker>() {} - - // Declared relationships - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); -} +use crate::variant::{VariantParam, VariantTypeEntry}; #[test] fn test_graph_type_traits() { @@ -53,7 +8,8 @@ fn test_graph_type_traits() { let _: PlanarGraph = Default::default(); let _: UnitDiskGraph = Default::default(); let _: BipartiteGraph = Default::default(); - let _: GridGraph = Default::default(); + let _: KingsSubgraph = Default::default(); + let _: TriangularSubgraph = Default::default(); let _: HyperGraph = Default::default(); // Test Copy (SimpleGraph implements Copy, so no need to clone) @@ -65,66 +21,110 @@ fn test_graph_type_traits() { } #[test] -fn test_bipartite_entry_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - assert!(entries - .iter() - .any(|e| e.subtype == "BipartiteGraph" && e.supertype == "SimpleGraph")); -} - -#[test] -fn test_unit_disk_to_planar_not_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - // UnitDiskGraph => PlanarGraph was removed (incorrect relationship) - assert!(!entries - .iter() - .any(|e| e.subtype == "UnitDiskGraph" && e.supertype == "PlanarGraph")); +fn test_planargraph_variant_param() { + assert_eq!(PlanarGraph::CATEGORY, "graph"); + assert_eq!(PlanarGraph::VALUE, "PlanarGraph"); + assert_eq!(PlanarGraph::PARENT_VALUE, Some("SimpleGraph")); } #[test] -fn test_gridgraph_subtypes() { - fn assert_subtype, B: GraphMarker>() {} - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); +fn test_bipartitegraph_variant_param() { + assert_eq!(BipartiteGraph::CATEGORY, "graph"); + assert_eq!(BipartiteGraph::VALUE, "BipartiteGraph"); + assert_eq!(BipartiteGraph::PARENT_VALUE, Some("SimpleGraph")); } #[test] -fn test_hypergraph_subtypes() { - fn assert_subtype, B: GraphMarker>() {} - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); - assert_subtype::(); +fn test_graph_variant_type_entries_registered() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "graph") + .collect(); + + // Should include PlanarGraph, BipartiteGraph, and the topology types + assert!( + entries + .iter() + .any(|e| e.value == "PlanarGraph" && e.parent == Some("SimpleGraph")), + "PlanarGraph should be registered with parent SimpleGraph" + ); + assert!( + entries + .iter() + .any(|e| e.value == "BipartiteGraph" && e.parent == Some("SimpleGraph")), + "BipartiteGraph should be registered with parent SimpleGraph" + ); + assert!( + entries.iter().any(|e| e.value == "SimpleGraph"), + "SimpleGraph should be registered" + ); + assert!( + entries.iter().any(|e| e.value == "UnitDiskGraph"), + "UnitDiskGraph should be registered" + ); + assert!( + entries.iter().any(|e| e.value == "KingsSubgraph"), + "KingsSubgraph should be registered" + ); + assert!( + entries.iter().any(|e| e.value == "TriangularSubgraph"), + "TriangularSubgraph should be registered" + ); + assert!( + entries.iter().any(|e| e.value == "HyperGraph"), + "HyperGraph should be registered" + ); } #[test] -fn test_gridgraph_entries_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - assert!(entries - .iter() - .any(|e| e.subtype == "GridGraph" && e.supertype == "UnitDiskGraph")); +fn test_weight_variant_type_entries_registered() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "weight") + .collect(); + + assert!( + entries + .iter() + .any(|e| e.value == "One" && e.parent == Some("i32")), + "One should be registered with parent i32" + ); + assert!( + entries + .iter() + .any(|e| e.value == "i32" && e.parent == Some("f64")), + "i32 should be registered with parent f64" + ); + assert!( + entries + .iter() + .any(|e| e.value == "f64" && e.parent.is_none()), + "f64 should be registered as root" + ); } #[test] -fn test_hypergraph_entries_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - assert!(entries - .iter() - .any(|e| e.subtype == "SimpleGraph" && e.supertype == "HyperGraph")); +fn test_unitdiskgraph_to_planargraph_not_parent() { + // UnitDiskGraph's parent is SimpleGraph, not PlanarGraph + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "graph" && e.value == "UnitDiskGraph") + .collect(); + + for entry in &entries { + assert_ne!( + entry.parent, + Some("PlanarGraph"), + "UnitDiskGraph should not have PlanarGraph as parent" + ); + } } #[test] -fn test_weight_subtype_entries_registered() { - let entries: Vec<_> = inventory::iter::().collect(); - assert!(entries - .iter() - .any(|e| e.subtype == "One" && e.supertype == "i32")); - assert!(entries - .iter() - .any(|e| e.subtype == "i32" && e.supertype == "f64")); - assert!(entries - .iter() - .any(|e| e.subtype == "One" && e.supertype == "f64")); +fn test_marker_structs_exist() { + // Verify that all ZST marker structs still exist and can be instantiated + let _ = SimpleGraph; + let _ = PlanarGraph; + let _ = UnitDiskGraph; + let _ = BipartiteGraph; + let _ = KingsSubgraph; + let _ = TriangularSubgraph; + let _ = HyperGraph; } diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs index 0b57fbf6..8e77fcd2 100644 --- a/src/unit_tests/models/graph/kcoloring.rs +++ b/src/unit_tests/models/graph/kcoloring.rs @@ -1,22 +1,78 @@ use super::*; use crate::solvers::BruteForce; +use crate::variant::{K1, K2, K3, K4}; include!("../../jl_helpers.rs"); #[test] fn test_kcoloring_creation() { use crate::traits::Problem; - let problem = KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::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); assert_eq!(problem.dims(), vec![3, 3, 3, 3]); } +#[test] +fn test_evaluate_valid() { + use crate::traits::Problem; + + let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + + // Valid: different colors on adjacent vertices + assert!(problem.evaluate(&[0, 1, 0])); + assert!(problem.evaluate(&[0, 1, 2])); +} + +#[test] +fn test_evaluate_invalid() { + use crate::traits::Problem; + + let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); + + // Invalid: adjacent vertices have same color + assert!(!problem.evaluate(&[0, 0, 1])); + assert!(!problem.evaluate(&[0, 0, 0])); +} + +#[test] +fn test_brute_force_path() { + use crate::traits::Problem; + + // Path graph can be 2-colored + let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let solver = BruteForce::new(); + + let solutions = solver.find_all_satisfying(&problem); + // All solutions should be valid + for sol in &solutions { + assert!(problem.evaluate(sol)); + } +} + +#[test] +fn test_brute_force_triangle() { + use crate::traits::Problem; + + // Triangle needs 3 colors + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let solver = BruteForce::new(); + + let solutions = solver.find_all_satisfying(&problem); + for sol in &solutions { + assert!(problem.evaluate(sol)); + // All three vertices have different colors + assert_ne!(sol[0], sol[1]); + assert_ne!(sol[1], sol[2]); + assert_ne!(sol[0], sol[2]); + } +} + #[test] fn test_triangle_2_colors() { // Triangle cannot be 2-colored - let problem = KColoring::<2, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -40,7 +96,7 @@ fn test_is_valid_coloring_function() { fn test_empty_graph() { use crate::traits::Problem; - let problem = KColoring::<1, SimpleGraph>::new(3, vec![]); + let problem = KColoring::::new(3, vec![]); let solver = BruteForce::new(); let solutions = solver.find_all_satisfying(&problem); @@ -51,14 +107,42 @@ fn test_empty_graph() { } } +#[test] +fn test_complete_graph_k4() { + use crate::traits::Problem; + + // K4 needs 4 colors + let problem = + KColoring::::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); + for sol in &solutions { + assert!(problem.evaluate(sol)); + } +} + #[test] fn test_from_graph() { let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); - let problem = KColoring::<3, SimpleGraph>::from_graph(graph); + let problem = KColoring::::from_graph(graph); assert_eq!(problem.num_vertices(), 3); assert_eq!(problem.num_edges(), 2); } +#[test] +fn test_kcoloring_problem() { + use crate::traits::Problem; + + // Triangle graph with 3 colors + let p = KColoring::::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])); + // Invalid: vertices 0 and 1 same color + assert!(!p.evaluate(&[0, 0, 1])); +} + #[test] fn test_jl_parity_evaluation() { let data: serde_json::Value = @@ -67,12 +151,17 @@ fn test_jl_parity_evaluation() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); let num_edges = edges.len(); - let problem = KColoring::<3, SimpleGraph>::new(nv, edges); + let problem = KColoring::::new(nv, edges); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result: bool = problem.evaluate(&config); let jl_size = eval["size"].as_i64().unwrap() as usize; - assert_eq!(result, jl_size == num_edges, "KColoring mismatch for config {:?}", config); + assert_eq!( + result, + jl_size == num_edges, + "KColoring mismatch for config {:?}", + config + ); } let all_sat = BruteForce::new().find_all_satisfying(&problem); let jl_best = jl_parse_configs_set(&instance["best_solutions"]); diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index 59ebe065..59baefb1 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -111,7 +111,12 @@ fn test_jl_parity_evaluation() { let result = problem.evaluate(&config); let jl_size = eval["size"].as_i64().unwrap() as i32; assert!(result.is_valid(), "MaxCut should always be valid"); - assert_eq!(result.unwrap(), jl_size, "MaxCut size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "MaxCut size mismatch for config {:?}", + config + ); } let best = BruteForce::new().find_all_best(&problem); let jl_best = jl_parse_configs_set(&instance["best_solutions"]); diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 4c5bd1d5..ebc2f51f 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -154,10 +154,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "MaximalIS validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "MaximalIS validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "MaximalIS size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "MaximalIS size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index dd98bb0a..db313070 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -116,8 +116,10 @@ fn test_problem_name() { #[test] fn test_jl_parity_evaluation() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../../tests/data/jl/independentset.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../../tests/data/jl/independentset.json" + )) + .unwrap(); for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); @@ -131,10 +133,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "IS validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "IS validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "IS size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "IS size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index 6e6c754f..871aae65 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -129,10 +129,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "Matching validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "Matching validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "Matching size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "Matching size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index d0b7605f..838a2e7b 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -133,10 +133,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "DS validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "DS validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "DS size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "DS size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index 03881f5c..e95c1b18 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -101,8 +101,10 @@ fn test_has_edge() { #[test] fn test_jl_parity_evaluation() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../../tests/data/jl/vertexcovering.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../../tests/data/jl/vertexcovering.json" + )) + .unwrap(); for instance in data["instances"].as_array().unwrap() { let nv = instance["instance"]["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(&instance["instance"]); @@ -116,10 +118,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "VC validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "VC validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "VC size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "VC size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/optimization/qubo.rs b/src/unit_tests/models/optimization/qubo.rs index 86becd57..c08827b2 100644 --- a/src/unit_tests/models/optimization/qubo.rs +++ b/src/unit_tests/models/optimization/qubo.rs @@ -72,8 +72,16 @@ fn test_jl_parity_evaluation() { serde_json::from_str(include_str!("../../../../tests/data/jl/qubo.json")).unwrap(); for instance in data["instances"].as_array().unwrap() { let jl_matrix: Vec> = instance["instance"]["matrix"] - .as_array().unwrap().iter() - .map(|row| row.as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect()) + .as_array() + .unwrap() + .iter() + .map(|row| { + row.as_array() + .unwrap() + .iter() + .map(|v| v.as_f64().unwrap()) + .collect() + }) .collect(); let n = jl_matrix.len(); let mut rust_matrix = vec![vec![0.0f64; n]; n]; @@ -89,7 +97,11 @@ fn test_jl_parity_evaluation() { let result: SolutionSize = Problem::evaluate(&problem, &config); let jl_size = eval["size"].as_f64().unwrap(); assert!(result.is_valid(), "QUBO should always be valid"); - assert!((result.unwrap() - jl_size).abs() < 1e-10, "QUBO value mismatch for config {:?}", config); + assert!( + (result.unwrap() - jl_size).abs() < 1e-10, + "QUBO value mismatch for config {:?}", + config + ); } let best = BruteForce::new().find_all_best(&problem); let jl_best = jl_parse_configs_set(&instance["best_solutions"]); diff --git a/src/unit_tests/models/optimization/spin_glass.rs b/src/unit_tests/models/optimization/spin_glass.rs index b61c14e2..b9432f56 100644 --- a/src/unit_tests/models/optimization/spin_glass.rs +++ b/src/unit_tests/models/optimization/spin_glass.rs @@ -123,7 +123,12 @@ fn test_jl_parity_evaluation() { let result = problem.evaluate(&config); let jl_size = eval["size"].as_i64().unwrap() as i32; assert!(result.is_valid(), "SpinGlass should always be valid"); - assert_eq!(result.unwrap(), jl_size, "SpinGlass energy mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "SpinGlass energy mismatch for config {:?}", + config + ); } let best = BruteForce::new().find_all_best(&problem); let jl_best = jl_flip_configs_set(&jl_parse_configs_set(&instance["best_solutions"])); diff --git a/src/unit_tests/models/satisfiability/ksat.rs b/src/unit_tests/models/satisfiability/ksat.rs index ec8b2960..8e9786e1 100644 --- a/src/unit_tests/models/satisfiability/ksat.rs +++ b/src/unit_tests/models/satisfiability/ksat.rs @@ -1,11 +1,12 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::Problem; +use crate::variant::{K2, K3}; include!("../../jl_helpers.rs"); #[test] fn test_3sat_creation() { - let problem = KSatisfiability::<3>::new( + let problem = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![1, 2, 3]), @@ -19,12 +20,12 @@ fn test_3sat_creation() { #[test] #[should_panic(expected = "Clause 0 has 2 literals, expected 3")] fn test_3sat_wrong_clause_size() { - let _ = KSatisfiability::<3>::new(3, vec![CNFClause::new(vec![1, 2])]); + let _ = KSatisfiability::::new(3, vec![CNFClause::new(vec![1, 2])]); } #[test] fn test_2sat_creation() { - let problem = KSatisfiability::<2>::new( + let problem = KSatisfiability::::new( 2, vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], ); @@ -35,7 +36,7 @@ fn test_2sat_creation() { #[test] fn test_3sat_is_satisfying() { // (x1 OR x2 OR x3) AND (NOT x1 OR NOT x2 OR NOT x3) - let problem = KSatisfiability::<3>::new( + let problem = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![1, 2, 3]), @@ -49,29 +50,47 @@ fn test_3sat_is_satisfying() { assert!(!problem.is_satisfying(&[true, true, true])); } +#[test] +fn test_3sat_brute_force() { + let problem = KSatisfiability::::new( + 3, + vec![ + CNFClause::new(vec![1, 2, 3]), + CNFClause::new(vec![-1, -2, 3]), + ], + ); + let solver = BruteForce::new(); + let solutions = solver.find_all_satisfying(&problem); + + assert!(!solutions.is_empty()); + for sol in &solutions { + assert!(problem.evaluate(sol)); + } +} + #[test] fn test_ksat_allow_less() { // This should work - clause has 2 literals which is <= 3 - let problem = KSatisfiability::<3>::new_allow_less(2, vec![CNFClause::new(vec![1, 2])]); + let problem = KSatisfiability::::new_allow_less(2, vec![CNFClause::new(vec![1, 2])]); assert_eq!(problem.num_clauses(), 1); } #[test] #[should_panic(expected = "Clause 0 has 4 literals, expected at most 3")] fn test_ksat_allow_less_too_many() { - let _ = KSatisfiability::<3>::new_allow_less(4, vec![CNFClause::new(vec![1, 2, 3, 4])]); + let _ = KSatisfiability::::new_allow_less(4, vec![CNFClause::new(vec![1, 2, 3, 4])]); } #[test] fn test_ksat_get_clause() { - let problem = KSatisfiability::<3>::new(3, vec![CNFClause::new(vec![1, 2, 3])]); + let problem = KSatisfiability::::new(3, vec![CNFClause::new(vec![1, 2, 3])]); assert_eq!(problem.get_clause(0), Some(&CNFClause::new(vec![1, 2, 3]))); assert_eq!(problem.get_clause(1), None); } #[test] fn test_ksat_count_satisfied() { - let problem = KSatisfiability::<3>::new( + let problem = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![1, 2, 3]), @@ -84,19 +103,75 @@ fn test_ksat_count_satisfied() { assert_eq!(problem.count_satisfied(&[true, false, false]), 2); } +#[test] +fn test_ksat_evaluate() { + let problem = KSatisfiability::::new( + 3, + vec![ + CNFClause::new(vec![1, 2, 3]), + CNFClause::new(vec![-1, -2, -3]), + ], + ); + assert!(problem.evaluate(&[1, 0, 0])); // x1=T, x2=F, x3=F + assert!(!problem.evaluate(&[1, 1, 1])); // x1=T, x2=T, x3=T +} + +#[test] +fn test_ksat_problem_v2() { + use crate::traits::Problem; + + let p = KSatisfiability::::new( + 3, + vec![ + CNFClause::new(vec![1, 2, 3]), + CNFClause::new(vec![-1, -2, -3]), + ], + ); + + assert_eq!(p.dims(), vec![2, 2, 2]); + assert!(p.evaluate(&[1, 0, 0])); + assert!(!p.evaluate(&[1, 1, 1])); + assert!(!p.evaluate(&[0, 0, 0])); + assert!(p.evaluate(&[1, 0, 1])); + assert_eq!( as Problem>::NAME, "KSatisfiability"); +} + +#[test] +fn test_ksat_problem_v2_2sat() { + use crate::traits::Problem; + + let p = KSatisfiability::::new( + 2, + vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])], + ); + + assert_eq!(p.dims(), vec![2, 2]); + assert!(p.evaluate(&[1, 0])); + assert!(p.evaluate(&[0, 1])); + assert!(!p.evaluate(&[1, 1])); + assert!(!p.evaluate(&[0, 0])); +} + #[test] fn test_jl_parity_evaluation() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../../tests/data/jl/ksatisfiability.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../../tests/data/jl/ksatisfiability.json" + )) + .unwrap(); for instance in data["instances"].as_array().unwrap() { let (num_vars, clauses) = jl_parse_sat_clauses(&instance["instance"]); let num_clauses = instance["instance"]["clauses"].as_array().unwrap().len(); - let problem = KSatisfiability::<3>::new(num_vars, clauses); + let problem = KSatisfiability::::new(num_vars, clauses); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let rust_result = problem.evaluate(&config); let jl_size = eval["size"].as_u64().unwrap() as usize; - assert_eq!(rust_result, jl_size == num_clauses, "KSat eval mismatch for config {:?}", config); + assert_eq!( + rust_result, + jl_size == num_clauses, + "KSat eval mismatch for config {:?}", + config + ); } let rust_best = BruteForce::new().find_all_satisfying(&problem); let jl_best = jl_parse_configs_set(&instance["best_solutions"]); diff --git a/src/unit_tests/models/satisfiability/sat.rs b/src/unit_tests/models/satisfiability/sat.rs index f0d0fdfc..42f2e25f 100644 --- a/src/unit_tests/models/satisfiability/sat.rs +++ b/src/unit_tests/models/satisfiability/sat.rs @@ -167,8 +167,10 @@ fn test_clause_debug() { #[test] fn test_jl_parity_evaluation() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../../tests/data/jl/satisfiability.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../../tests/data/jl/satisfiability.json" + )) + .unwrap(); for instance in data["instances"].as_array().unwrap() { let (num_vars, clauses) = jl_parse_sat_clauses(&instance["instance"]); let problem = Satisfiability::new(num_vars, clauses); @@ -178,7 +180,11 @@ fn test_jl_parity_evaluation() { let rust_result = problem.evaluate(&config); let jl_size = eval["size"].as_u64().unwrap() as usize; let jl_all_satisfied = jl_size == num_clauses; - assert_eq!(rust_result, jl_all_satisfied, "SAT eval mismatch for config {:?}", config); + assert_eq!( + rust_result, jl_all_satisfied, + "SAT eval mismatch for config {:?}", + config + ); } let rust_best = BruteForce::new().find_all_satisfying(&problem); let rust_best_set: HashSet> = rust_best.into_iter().collect(); @@ -188,4 +194,3 @@ fn test_jl_parity_evaluation() { } } } - diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 1aab88c9..86625a60 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -113,10 +113,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "SetPacking validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "SetPacking validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "SetPacking size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "SetPacking size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs index 65ef74e8..fa9036e5 100644 --- a/src/unit_tests/models/set/minimum_set_covering.rs +++ b/src/unit_tests/models/set/minimum_set_covering.rs @@ -84,10 +84,20 @@ fn test_jl_parity_evaluation() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); - assert_eq!(result.is_valid(), jl_valid, "SetCovering validity mismatch for config {:?}", config); + assert_eq!( + result.is_valid(), + jl_valid, + "SetCovering validity mismatch for config {:?}", + config + ); if jl_valid { let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "SetCovering size mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "SetCovering size mismatch for config {:?}", + config + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs index cfe62ee5..1acd0892 100644 --- a/src/unit_tests/models/specialized/factoring.rs +++ b/src/unit_tests/models/specialized/factoring.rs @@ -77,9 +77,17 @@ fn test_jl_parity_evaluation() { let result = problem.evaluate(&config); let jl_valid = eval["is_valid"].as_bool().unwrap(); if jl_valid { - assert_eq!(result.unwrap(), 0, "Factoring: valid config should have distance 0"); + assert_eq!( + result.unwrap(), + 0, + "Factoring: valid config should have distance 0" + ); } else { - assert_ne!(result.unwrap(), 0, "Factoring: invalid config should have nonzero distance"); + assert_ne!( + result.unwrap(), + 0, + "Factoring: invalid config should have nonzero distance" + ); } } let best = BruteForce::new().find_all_best(&problem); diff --git a/src/unit_tests/models/specialized/paintshop.rs b/src/unit_tests/models/specialized/paintshop.rs index 040f0994..ce57c287 100644 --- a/src/unit_tests/models/specialized/paintshop.rs +++ b/src/unit_tests/models/specialized/paintshop.rs @@ -107,14 +107,22 @@ fn test_jl_parity_evaluation() { serde_json::from_str(include_str!("../../../../tests/data/jl/paintshop.json")).unwrap(); for instance in data["instances"].as_array().unwrap() { let sequence: Vec = instance["instance"]["sequence"] - .as_array().unwrap().iter() - .map(|v| v.as_str().unwrap().to_string()).collect(); + .as_array() + .unwrap() + .iter() + .map(|v| v.as_str().unwrap().to_string()) + .collect(); let problem = PaintShop::new(sequence); for eval in instance["evaluations"].as_array().unwrap() { let config = jl_parse_config(&eval["config"]); let result = problem.evaluate(&config); let jl_size = eval["size"].as_i64().unwrap() as i32; - assert_eq!(result.unwrap(), jl_size, "PaintShop switches mismatch for config {:?}", config); + assert_eq!( + result.unwrap(), + jl_size, + "PaintShop switches mismatch for config {:?}", + config + ); } let best = BruteForce::new().find_all_best(&problem); let jl_best = jl_parse_configs_set(&instance["best_solutions"]); diff --git a/src/unit_tests/rules/circuit_spinglass.rs b/src/unit_tests/rules/circuit_spinglass.rs index a362ffec..eb84963d 100644 --- a/src/unit_tests/rules/circuit_spinglass.rs +++ b/src/unit_tests/rules/circuit_spinglass.rs @@ -9,6 +9,7 @@ include!("../jl_helpers.rs"); fn verify_gadget_truth_table(gadget: &LogicGadget, expected: &[(Vec, Vec)]) where W: WeightElement + + crate::variant::VariantParam + PartialOrd + Num + Zero @@ -281,7 +282,11 @@ fn test_jl_parity_circuitsat_to_spinglass() { let c = BooleanExpr::var("c"); let x_expr = BooleanExpr::or(vec![a.clone(), BooleanExpr::not(b.clone())]); let y_expr = BooleanExpr::or(vec![BooleanExpr::not(c.clone()), b.clone()]); - let z_expr = BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y"), a.clone()]); + let z_expr = BooleanExpr::and(vec![ + BooleanExpr::var("x"), + BooleanExpr::var("y"), + a.clone(), + ]); let circuit = Circuit::new(vec![ Assignment::new(vec!["x".to_string()], x_expr), Assignment::new(vec!["y".to_string()], y_expr), @@ -292,7 +297,13 @@ fn test_jl_parity_circuitsat_to_spinglass() { let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source = solver.find_all_satisfying(&source); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); let best_source_set: HashSet> = best_source.into_iter().collect(); - assert!(extracted.is_subset(&best_source_set), "CircuitSAT->SpinGlass: extracted not satisfying"); + assert!( + extracted.is_subset(&best_source_set), + "CircuitSAT->SpinGlass: extracted not satisfying" + ); } diff --git a/src/unit_tests/rules/coloring_ilp.rs b/src/unit_tests/rules/coloring_ilp.rs index ffdd6a9c..f296dc44 100644 --- a/src/unit_tests/rules/coloring_ilp.rs +++ b/src/unit_tests/rules/coloring_ilp.rs @@ -1,11 +1,12 @@ use super::*; use crate::solvers::{BruteForce, ILPSolver}; use crate::traits::Problem; +use crate::variant::{K1, K2, K3, K4}; #[test] fn test_reduction_creates_valid_ilp() { // Triangle graph with 3 colors - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -36,7 +37,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>::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -50,7 +51,7 @@ fn test_reduction_path_graph() { #[test] fn test_ilp_solution_equals_brute_force_triangle() { // Triangle needs 3 colors - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -83,7 +84,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>::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -108,7 +109,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>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let problem = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -124,7 +125,7 @@ fn test_ilp_infeasible_triangle_2_colors() { #[test] fn test_solution_extraction() { - let problem = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1)]); + let problem = KColoring::::new(3, vec![(0, 1)]); let reduction = ReduceTo::::reduce_to(&problem); // ILP solution where: @@ -143,7 +144,7 @@ fn test_solution_extraction() { #[test] fn test_ilp_structure() { - let problem = KColoring::<3, SimpleGraph>::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); + let problem = KColoring::::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -156,7 +157,7 @@ fn test_ilp_structure() { #[test] fn test_empty_graph() { // Graph with no edges: any coloring is valid - let problem = KColoring::<1, SimpleGraph>::new(3, vec![]); + let problem = KColoring::::new(3, vec![]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -174,7 +175,7 @@ fn test_empty_graph() { fn test_complete_graph_k4() { // K4 needs 4 colors let problem = - KColoring::<4, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + KColoring::::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -195,7 +196,7 @@ fn test_complete_graph_k4() { fn test_complete_graph_k4_with_3_colors_infeasible() { // K4 cannot be 3-colored let problem = - KColoring::<3, SimpleGraph>::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); + KColoring::::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -208,7 +209,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>::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); + let problem = KColoring::::new(4, vec![(0, 2), (0, 3), (1, 2), (1, 3)]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -228,7 +229,7 @@ fn test_bipartite_graph() { #[test] fn test_solve_reduced() { // Test the ILPSolver::solve_reduced method - let problem = KColoring::<2, SimpleGraph>::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = KColoring::::new(4, vec![(0, 1), (1, 2), (2, 3)]); let ilp_solver = ILPSolver::new(); let solution = ilp_solver @@ -241,7 +242,7 @@ fn test_solve_reduced() { #[test] fn test_single_vertex() { // Single vertex graph: always 1-colorable - let problem = KColoring::<1, SimpleGraph>::new(1, vec![]); + let problem = KColoring::::new(1, vec![]); let reduction = ReduceTo::::reduce_to(&problem); let ilp = reduction.target_problem(); @@ -258,7 +259,7 @@ fn test_single_vertex() { #[test] fn test_single_edge() { // Single edge: needs 2 colors - let problem = KColoring::<2, SimpleGraph>::new(2, vec![(0, 1)]); + let problem = KColoring::::new(2, vec![(0, 1)]); let reduction = ReduceTo::::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 5d1b80c4..b4a48a60 100644 --- a/src/unit_tests/rules/coloring_qubo.rs +++ b/src/unit_tests/rules/coloring_qubo.rs @@ -1,11 +1,12 @@ use super::*; use crate::solvers::BruteForce; use crate::traits::Problem; +use crate::variant::{K2, K3}; #[test] fn test_kcoloring_to_qubo_closed_loop() { // Triangle K3, 3 colors → exactly 6 valid colorings (3! permutations) - let kc = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let kc = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -25,7 +26,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>::new(3, vec![(0, 1), (1, 2)]); + let kc = KColoring::::new(3, vec![(0, 1), (1, 2)]); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -45,7 +46,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>::new(3, vec![(2, 0), (0, 1)]); + let kc = KColoring::::new(3, vec![(2, 0), (0, 1)]); let reduction = ReduceTo::>::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -63,7 +64,7 @@ fn test_kcoloring_to_qubo_reversed_edges() { #[test] fn test_kcoloring_to_qubo_sizes() { - let kc = KColoring::<3, SimpleGraph>::new(3, vec![(0, 1), (1, 2), (0, 2)]); + let kc = KColoring::::new(3, vec![(0, 1), (1, 2), (0, 2)]); let reduction = ReduceTo::>::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 38375b76..c55726d6 100644 --- a/src/unit_tests/rules/factoring_circuit.rs +++ b/src/unit_tests/rules/factoring_circuit.rs @@ -304,11 +304,20 @@ fn test_jl_parity_factoring_to_circuitsat() { let best_target = solver.find_all_satisfying(result.target_problem()); for t in &best_target { let sol = result.extract_solution(t); - assert_eq!(source.evaluate(&sol).unwrap(), 0, "Factoring extracted solution should be valid"); + assert_eq!( + source.evaluate(&sol).unwrap(), + 0, + "Factoring extracted solution should be valid" + ); } - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/factoring_to_circuitsat.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/factoring_to_circuitsat.json" + )) + .unwrap(); let jl_best_source = jl_parse_configs_set(&data["cases"][0]["best_source"]); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - assert_eq!(best_source, jl_best_source, "Factoring best source mismatch"); + assert_eq!( + best_source, jl_best_source, + "Factoring best source mismatch" + ); } diff --git a/src/unit_tests/rules/graph.rs b/src/unit_tests/rules/graph.rs index 9011e05b..6e7efdc0 100644 --- a/src/unit_tests/rules/graph.rs +++ b/src/unit_tests/rules/graph.rs @@ -4,6 +4,36 @@ use crate::models::set::MaximumSetPacking; use crate::rules::cost::MinimizeSteps; use crate::topology::SimpleGraph; +#[test] +fn test_resolved_path_basic_structure() { + use crate::rules::graph::{EdgeKind, ReductionStep, ResolvedPath}; + use std::collections::BTreeMap; + + let steps = vec![ + ReductionStep { + name: "A".to_string(), + variant: BTreeMap::from([("graph".to_string(), "SimpleGraph".to_string())]), + }, + ReductionStep { + name: "B".to_string(), + variant: BTreeMap::from([("weight".to_string(), "f64".to_string())]), + }, + ]; + let edges = vec![EdgeKind::Reduction { + overhead: Default::default(), + }]; + let path = ResolvedPath { + steps: steps.clone(), + edges, + }; + + assert_eq!(path.len(), 1); + assert_eq!(path.num_reductions(), 1); + assert_eq!(path.num_casts(), 0); + assert_eq!(path.steps[0].name, "A"); + assert_eq!(path.steps[1].name, "B"); +} + #[test] fn test_find_direct_path() { let graph = ReductionGraph::new(); @@ -227,6 +257,7 @@ fn test_sat_based_reductions() { use crate::models::graph::KColoring; use crate::models::graph::MinimumDominatingSet; use crate::models::satisfiability::Satisfiability; + use crate::variant::K3; let graph = ReductionGraph::new(); @@ -234,7 +265,7 @@ fn test_sat_based_reductions() { assert!(graph.has_direct_reduction::>()); // SAT -> KColoring - assert!(graph.has_direct_reduction::>()); + assert!(graph.has_direct_reduction::>()); // SAT -> MinimumDominatingSet assert!(graph.has_direct_reduction::>()); @@ -281,12 +312,13 @@ fn test_optimization_reductions() { #[test] fn test_ksat_reductions() { use crate::models::satisfiability::{KSatisfiability, Satisfiability}; + use crate::variant::K3; let graph = ReductionGraph::new(); // SAT <-> 3-SAT (bidirectional) - assert!(graph.has_direct_reduction::>()); - assert!(graph.has_direct_reduction::, Satisfiability>()); + assert!(graph.has_direct_reduction::>()); + assert!(graph.has_direct_reduction::, Satisfiability>()); } #[test] @@ -431,7 +463,7 @@ fn test_graph_hierarchy_built() { let graph = ReductionGraph::new(); let hierarchy = graph.graph_hierarchy(); - // Should have relationships from GraphSubtypeEntry registrations + // Should have relationships from VariantTypeEntry registrations // UnitDiskGraph -> PlanarGraph -> SimpleGraph // BipartiteGraph -> SimpleGraph assert!( @@ -467,7 +499,7 @@ fn test_is_graph_subtype_direct() { // Direct subtype relationships assert!(graph.is_graph_subtype("PlanarGraph", "SimpleGraph")); assert!(graph.is_graph_subtype("BipartiteGraph", "SimpleGraph")); - assert!(graph.is_graph_subtype("GridGraph", "UnitDiskGraph")); + assert!(graph.is_graph_subtype("KingsSubgraph", "UnitDiskGraph")); assert!(graph.is_graph_subtype("UnitDiskGraph", "SimpleGraph")); assert!(graph.is_graph_subtype("SimpleGraph", "HyperGraph")); } @@ -476,9 +508,9 @@ fn test_is_graph_subtype_direct() { fn test_is_graph_subtype_transitive() { let graph = ReductionGraph::new(); - // Transitive closure: GridGraph -> UnitDiskGraph -> SimpleGraph -> HyperGraph - assert!(graph.is_graph_subtype("GridGraph", "SimpleGraph")); - assert!(graph.is_graph_subtype("GridGraph", "HyperGraph")); + // Transitive closure: KingsSubgraph -> UnitDiskGraph -> SimpleGraph -> HyperGraph + assert!(graph.is_graph_subtype("KingsSubgraph", "SimpleGraph")); + assert!(graph.is_graph_subtype("KingsSubgraph", "HyperGraph")); assert!(graph.is_graph_subtype("UnitDiskGraph", "HyperGraph")); } @@ -779,12 +811,12 @@ fn test_reduction_variant_nodes_in_json() { let graph = ReductionGraph::new(); let json = graph.to_json(); - // GridGraph variants should appear as nodes - let mis_gridgraph = json.nodes.iter().any(|n| { + // KingsSubgraph variants should appear as nodes + let mis_kingssubgraph = json.nodes.iter().any(|n| { n.name == "MaximumIndependentSet" - && n.variant.get("graph") == Some(&"GridGraph".to_string()) + && n.variant.get("graph") == Some(&"KingsSubgraph".to_string()) }); - assert!(mis_gridgraph, "MIS/GridGraph node should exist"); + assert!(mis_kingssubgraph, "MIS/KingsSubgraph node should exist"); let mis_unitdisk = json.nodes.iter().any(|n| { n.name == "MaximumIndependentSet" @@ -792,7 +824,7 @@ fn test_reduction_variant_nodes_in_json() { }); assert!(mis_unitdisk, "MIS/UnitDiskGraph node should exist"); - // MaxCut/GridGraph was removed (orphan with no reduction path) + // MaxCut/Grid was removed (orphan with no reduction path) } #[test] @@ -800,16 +832,16 @@ fn test_natural_edge_graph_relaxation() { let graph = ReductionGraph::new(); let json = graph.to_json(); - // MIS/GridGraph -> MIS/SimpleGraph should exist (graph type relaxation) + // MIS/KingsSubgraph -> MIS/SimpleGraph should exist (graph type relaxation) let has_edge = json.edges.iter().any(|e| { json.source_node(e).name == "MaximumIndependentSet" && json.target_node(e).name == "MaximumIndependentSet" - && json.source_node(e).variant.get("graph") == Some(&"GridGraph".to_string()) + && json.source_node(e).variant.get("graph") == Some(&"KingsSubgraph".to_string()) && json.target_node(e).variant.get("graph") == Some(&"SimpleGraph".to_string()) }); assert!( has_edge, - "Natural edge MIS/GridGraph -> MIS/SimpleGraph should exist" + "Natural edge MIS/KingsSubgraph -> MIS/SimpleGraph should exist" ); } @@ -818,16 +850,16 @@ fn test_natural_edge_triangular_to_simplegraph() { let graph = ReductionGraph::new(); let json = graph.to_json(); - // MIS/Triangular -> MIS/SimpleGraph should exist (Triangular is a subtype of SimpleGraph) + // MIS/TriangularSubgraph -> MIS/SimpleGraph should exist (TriangularSubgraph is a subtype of SimpleGraph) let has_edge = json.edges.iter().any(|e| { json.source_node(e).name == "MaximumIndependentSet" && json.target_node(e).name == "MaximumIndependentSet" - && json.source_node(e).variant.get("graph") == Some(&"Triangular".to_string()) + && json.source_node(e).variant.get("graph") == Some(&"TriangularSubgraph".to_string()) && json.target_node(e).variant.get("graph") == Some(&"SimpleGraph".to_string()) }); assert!( has_edge, - "Natural edge MIS/Triangular -> MIS/SimpleGraph should exist" + "Natural edge MIS/TriangularSubgraph -> MIS/SimpleGraph should exist" ); } @@ -836,16 +868,16 @@ fn test_natural_edge_gridgraph_to_unitdisk() { let graph = ReductionGraph::new(); let json = graph.to_json(); - // MIS/GridGraph -> MIS/UnitDiskGraph should exist + // MIS/KingsSubgraph -> MIS/UnitDiskGraph should exist let has_edge = json.edges.iter().any(|e| { json.source_node(e).name == "MaximumIndependentSet" && json.target_node(e).name == "MaximumIndependentSet" - && json.source_node(e).variant.get("graph") == Some(&"GridGraph".to_string()) + && json.source_node(e).variant.get("graph") == Some(&"KingsSubgraph".to_string()) && json.target_node(e).variant.get("graph") == Some(&"UnitDiskGraph".to_string()) }); assert!( has_edge, - "Natural edge MIS/GridGraph -> MIS/UnitDiskGraph should exist" + "Natural edge MIS/KingsSubgraph -> MIS/UnitDiskGraph should exist" ); } @@ -854,18 +886,18 @@ fn test_no_natural_edge_wrong_direction() { let graph = ReductionGraph::new(); let json = graph.to_json(); - // No NATURAL edge from SimpleGraph -> GridGraph (wrong direction for graph relaxation). - // A real reduction edge from SimpleGraph -> GridGraph may exist (unit disk mapping). + // No NATURAL edge from SimpleGraph -> KingsSubgraph (wrong direction for graph relaxation). + // A real reduction edge from SimpleGraph -> KingsSubgraph may exist (unit disk mapping). let has_natural_edge = json.edges.iter().any(|e| { json.source_node(e).name == "MaximumIndependentSet" && json.target_node(e).name == "MaximumIndependentSet" && json.source_node(e).variant.get("graph") == Some(&"SimpleGraph".to_string()) - && json.target_node(e).variant.get("graph") == Some(&"GridGraph".to_string()) + && json.target_node(e).variant.get("graph") == Some(&"KingsSubgraph".to_string()) && e.doc_path.is_empty() // natural edges have empty doc_path }); assert!( !has_natural_edge, - "Should NOT have natural edge MIS/SimpleGraph -> MIS/GridGraph" + "Should NOT have natural edge MIS/SimpleGraph -> MIS/KingsSubgraph" ); } @@ -896,7 +928,7 @@ fn test_natural_edge_has_identity_overhead() { let natural_edge = json.edges.iter().find(|e| { json.source_node(e).name == "MaximumIndependentSet" && json.target_node(e).name == "MaximumIndependentSet" - && json.source_node(e).variant.get("graph") == Some(&"GridGraph".to_string()) + && json.source_node(e).variant.get("graph") == Some(&"KingsSubgraph".to_string()) && json.target_node(e).variant.get("graph") == Some(&"SimpleGraph".to_string()) && json.source_node(e).variant.get("weight") == Some(&"i32".to_string()) && json.target_node(e).variant.get("weight") == Some(&"i32".to_string()) @@ -916,3 +948,212 @@ fn test_natural_edge_has_identity_overhead() { ); } } + +#[test] +fn test_find_matching_entry_ksat_k3() { + let graph = ReductionGraph::new(); + let variant_k3: std::collections::BTreeMap = + [("k".to_string(), "K3".to_string())].into(); + + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant_k3); + assert!(entry.is_some()); + let entry = entry.unwrap(); + let source_var = &entry.source_variant; + let overhead = &entry.overhead; + // K=3 overhead has num_clauses term; K=2 does not + assert!(overhead + .output_size + .iter() + .any(|(field, _)| *field == "num_vars")); + // K=3 overhead: poly!(num_vars) + poly!(num_clauses) → two terms total + let num_vars_poly = &overhead + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert!( + num_vars_poly.terms.len() >= 2, + "K=3 overhead should have num_vars + num_clauses" + ); + // Verify the source variant matches k=K3 + assert_eq!(source_var.get("k"), Some(&"K3".to_string())); +} + +#[test] +fn test_find_matching_entry_ksat_k2() { + let graph = ReductionGraph::new(); + let variant_k2: std::collections::BTreeMap = + [("k".to_string(), "K2".to_string())].into(); + + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant_k2); + assert!(entry.is_some()); + let entry = entry.unwrap(); + let overhead = &entry.overhead; + // K=2 overhead: just poly!(num_vars) → one term + let num_vars_poly = &overhead + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert_eq!( + num_vars_poly.terms.len(), + 1, + "K=2 overhead should have only num_vars" + ); +} + +#[test] +fn test_find_matching_entry_no_match() { + let graph = ReductionGraph::new(); + let variant: std::collections::BTreeMap = + [("k".to_string(), "K99".to_string())].into(); + + // k=K99 is not a subtype of K2 or K3 + let entry = graph.find_best_entry("KSatisfiability", "QUBO", &variant); + assert!(entry.is_none()); +} + +#[test] +fn test_resolve_path_direct_same_variant() { + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + // MIS(SimpleGraph, i32) → VC(SimpleGraph, i32) — no cast needed + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + assert_eq!(resolved.num_reductions(), 1); + assert_eq!(resolved.num_casts(), 0); + assert_eq!(resolved.steps.len(), 2); + assert_eq!(resolved.steps[0].name, "MaximumIndependentSet"); + assert_eq!(resolved.steps[1].name, "MinimumVertexCover"); +} + +#[test] +fn test_resolve_path_with_natural_cast() { + use crate::topology::KingsSubgraph; + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + // MIS(KingsSubgraph) → VC(SimpleGraph) — needs a natural cast MIS(KingsSubgraph)→MIS(SimpleGraph) + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "KingsSubgraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + // Should be: MIS(KingsSubgraph) --NaturalCast--> MIS(SimpleGraph) --Reduction--> VC(SimpleGraph) + assert_eq!(resolved.num_reductions(), 1); + assert_eq!(resolved.num_casts(), 1); + assert_eq!(resolved.steps.len(), 3); + assert_eq!(resolved.steps[0].name, "MaximumIndependentSet"); + assert_eq!( + resolved.steps[0].variant.get("graph").unwrap(), + "KingsSubgraph" + ); + assert_eq!(resolved.steps[1].name, "MaximumIndependentSet"); + assert_eq!( + resolved.steps[1].variant.get("graph").unwrap(), + "SimpleGraph" + ); + assert_eq!(resolved.steps[2].name, "MinimumVertexCover"); + assert!(matches!(resolved.edges[0], EdgeKind::NaturalCast)); + assert!(matches!(resolved.edges[1], EdgeKind::Reduction { .. })); +} + +#[test] +fn test_resolve_path_ksat_disambiguates() { + use crate::rules::graph::EdgeKind; + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + let name_path = graph + .find_shortest_path_by_name("KSatisfiability", "QUBO") + .unwrap(); + + // Resolve with k=K3 + let source_k3 = BTreeMap::from([("k".to_string(), "K3".to_string())]); + let target = BTreeMap::from([("weight".to_string(), "f64".to_string())]); + + let resolved_k3 = graph.resolve_path(&name_path, &source_k3, &target).unwrap(); + assert_eq!(resolved_k3.num_reductions(), 1); + + // Extract overhead from the reduction edge + let overhead_k3 = match &resolved_k3.edges.last().unwrap() { + EdgeKind::Reduction { overhead } => overhead, + _ => panic!("last edge should be Reduction"), + }; + // K=3 overhead has 2 terms in num_vars polynomial + let num_vars_poly_k3 = &overhead_k3 + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert!(num_vars_poly_k3.terms.len() >= 2); + + // Resolve with k=K2 + let source_k2 = BTreeMap::from([("k".to_string(), "K2".to_string())]); + let resolved_k2 = graph.resolve_path(&name_path, &source_k2, &target).unwrap(); + let overhead_k2 = match &resolved_k2.edges.last().unwrap() { + EdgeKind::Reduction { overhead } => overhead, + _ => panic!("last edge should be Reduction"), + }; + let num_vars_poly_k2 = &overhead_k2 + .output_size + .iter() + .find(|(f, _)| *f == "num_vars") + .unwrap() + .1; + assert_eq!(num_vars_poly_k2.terms.len(), 1); +} + +#[test] +fn test_resolve_path_incompatible_returns_none() { + use std::collections::BTreeMap; + let graph = ReductionGraph::new(); + + let name_path = graph + .find_shortest_path_by_name("KSatisfiability", "QUBO") + .unwrap(); + + // k=K99 matches neither K2 nor K3 + let source = BTreeMap::from([("k".to_string(), "K99".to_string())]); + let target = BTreeMap::from([("weight".to_string(), "f64".to_string())]); + + let resolved = graph.resolve_path(&name_path, &source, &target); + assert!(resolved.is_none()); +} diff --git a/src/unit_tests/rules/ksatisfiability_qubo.rs b/src/unit_tests/rules/ksatisfiability_qubo.rs index d8df1cb1..a3ec43e6 100644 --- a/src/unit_tests/rules/ksatisfiability_qubo.rs +++ b/src/unit_tests/rules/ksatisfiability_qubo.rs @@ -2,12 +2,13 @@ use super::*; use crate::models::satisfiability::CNFClause; use crate::solvers::BruteForce; use crate::traits::Problem; +use crate::variant::{K2, K3}; #[test] fn test_ksatisfiability_to_qubo_closed_loop() { // 3 vars, 4 clauses (matches ground truth): // (x1 ∨ x2), (¬x1 ∨ x3), (x2 ∨ ¬x3), (¬x2 ∨ ¬x3) - let ksat = KSatisfiability::<2>::new( + let ksat = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![1, 2]), // x1 ∨ x2 @@ -32,7 +33,7 @@ fn test_ksatisfiability_to_qubo_closed_loop() { #[test] fn test_ksatisfiability_to_qubo_simple() { // 2 vars, 1 clause: (x1 ∨ x2) → 3 satisfying assignments - let ksat = KSatisfiability::<2>::new(2, vec![CNFClause::new(vec![1, 2])]); + let ksat = KSatisfiability::::new(2, vec![CNFClause::new(vec![1, 2])]); let reduction = ReduceTo::>::reduce_to(&ksat); let qubo = reduction.target_problem(); @@ -50,7 +51,7 @@ fn test_ksatisfiability_to_qubo_contradiction() { // 1 var, 2 clauses: (x1 ∨ x1) and (¬x1 ∨ ¬x1) — can't satisfy both // Actually, this is (x1) and (¬x1), which is a contradiction // Max-2-SAT will satisfy 1 of 2 clauses - let ksat = KSatisfiability::<2>::new( + let ksat = KSatisfiability::::new( 1, vec![ CNFClause::new(vec![1, 1]), // x1 ∨ x1 = x1 @@ -71,7 +72,7 @@ fn test_ksatisfiability_to_qubo_contradiction() { fn test_ksatisfiability_to_qubo_reversed_vars() { // Clause (3, -1) has var_i=2 > var_j=0, triggering the swap branch (line 71). // 3 vars, clauses: (x3 ∨ ¬x1), (x1 ∨ x2) - let ksat = KSatisfiability::<2>::new( + let ksat = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![3, -1]), // var 2 > var 0 → swap @@ -92,7 +93,7 @@ fn test_ksatisfiability_to_qubo_reversed_vars() { #[test] fn test_ksatisfiability_to_qubo_structure() { - let ksat = KSatisfiability::<2>::new( + let ksat = KSatisfiability::::new( 3, vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])], ); @@ -106,7 +107,7 @@ fn test_ksatisfiability_to_qubo_structure() { #[test] fn test_k3satisfiability_to_qubo_closed_loop() { // 3-SAT: 5 vars, 7 clauses - let ksat = KSatisfiability::<3>::new( + let ksat = KSatisfiability::::new( 5, vec![ CNFClause::new(vec![1, 2, -3]), // x1 ∨ x2 ∨ ¬x3 @@ -140,7 +141,7 @@ fn test_k3satisfiability_to_qubo_closed_loop() { #[test] fn test_k3satisfiability_to_qubo_single_clause() { // Single 3-SAT clause: (x1 ∨ x2 ∨ x3) — 7 satisfying assignments - let ksat = KSatisfiability::<3>::new(3, vec![CNFClause::new(vec![1, 2, 3])]); + let ksat = KSatisfiability::::new(3, vec![CNFClause::new(vec![1, 2, 3])]); let reduction = ReduceTo::>::reduce_to(&ksat); let qubo = reduction.target_problem(); @@ -163,7 +164,7 @@ fn test_k3satisfiability_to_qubo_single_clause() { #[test] fn test_k3satisfiability_to_qubo_all_negated() { // All negated: (¬x1 ∨ ¬x2 ∨ ¬x3) — 7 satisfying assignments - let ksat = KSatisfiability::<3>::new(3, vec![CNFClause::new(vec![-1, -2, -3])]); + let ksat = KSatisfiability::::new(3, vec![CNFClause::new(vec![-1, -2, -3])]); let reduction = ReduceTo::>::reduce_to(&ksat); let qubo = reduction.target_problem(); diff --git a/src/unit_tests/rules/maximumindependentset_gridgraph.rs b/src/unit_tests/rules/maximumindependentset_gridgraph.rs index dfc39cda..93a0eda2 100644 --- a/src/unit_tests/rules/maximumindependentset_gridgraph.rs +++ b/src/unit_tests/rules/maximumindependentset_gridgraph.rs @@ -1,13 +1,13 @@ use super::*; use crate::models::graph::MaximumIndependentSet; use crate::solvers::BruteForce; -use crate::topology::{SimpleGraph, UnitDiskGraph}; +use crate::topology::{KingsSubgraph, SimpleGraph, UnitDiskGraph}; #[test] fn test_mis_simple_to_grid_closed_loop() { // Triangle graph: 3 vertices, 3 edges let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2), (0, 2)]); - let result = ReduceTo::, i32>>::reduce_to(&problem); + let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); // The grid graph should have more vertices than the original @@ -31,7 +31,7 @@ fn test_mis_simple_to_grid_closed_loop() { fn test_mis_simple_to_grid_path_graph() { // Path graph: 0-1-2 let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); - let result = ReduceTo::, i32>>::reduce_to(&problem); + let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); let solver = BruteForce::new(); @@ -53,7 +53,7 @@ fn test_mis_unitdisk_to_grid_closed_loop() { assert_eq!(udg.num_edges(), 1); let problem = MaximumIndependentSet::::from_graph(udg, vec![1, 1, 1]); - let result = ReduceTo::, i32>>::reduce_to(&problem); + let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); assert!(target.num_vertices() >= 3); diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index dbaae629..9d7c0f66 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -65,18 +65,25 @@ fn test_reduction_structure() { #[test] fn test_jl_parity_is_to_setpacking() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/independentset_to_setpacking.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/independentset_to_setpacking.json" + )) + .unwrap(); let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &is_data["instances"][0]["instance"]; let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -85,8 +92,10 @@ fn test_jl_parity_is_to_setpacking() { #[test] fn test_jl_parity_setpacking_to_is() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/setpacking_to_independentset.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/setpacking_to_independentset.json" + )) + .unwrap(); let sp_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/setpacking.json")).unwrap(); let inst = &sp_data["instances"][0]["instance"]; @@ -95,7 +104,10 @@ fn test_jl_parity_setpacking_to_is() { let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -104,18 +116,25 @@ fn test_jl_parity_setpacking_to_is() { #[test] fn test_jl_parity_rule_is_to_setpacking() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_independentset_to_setpacking.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_independentset_to_setpacking.json" + )) + .unwrap(); let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -124,19 +143,26 @@ fn test_jl_parity_rule_is_to_setpacking() { #[test] fn test_jl_parity_doc_is_to_setpacking() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/doc_independentset_to_setpacking.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/doc_independentset_to_setpacking.json" + )) + .unwrap(); let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let is_instance = jl_find_instance_by_label(&is_data, "doc_4vertex"); let inst = &is_instance["instance"]; let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/maximumindependentset_triangular.rs b/src/unit_tests/rules/maximumindependentset_triangular.rs index bfe54b02..dd240be3 100644 --- a/src/unit_tests/rules/maximumindependentset_triangular.rs +++ b/src/unit_tests/rules/maximumindependentset_triangular.rs @@ -1,12 +1,12 @@ use super::*; use crate::models::graph::MaximumIndependentSet; -use crate::topology::{Graph, SimpleGraph, Triangular}; +use crate::topology::{Graph, SimpleGraph, TriangularSubgraph}; #[test] fn test_mis_simple_to_triangular_closed_loop() { // Path graph: 0-1-2 let problem = MaximumIndependentSet::::new(3, vec![(0, 1), (1, 2)]); - let result = ReduceTo::>::reduce_to(&problem); + let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); // The triangular graph should have more vertices than the original @@ -22,11 +22,11 @@ fn test_mis_simple_to_triangular_closed_loop() { fn test_mis_simple_to_triangular_graph_methods() { // Single edge graph: 0-1 let problem = MaximumIndependentSet::::new(2, vec![(0, 1)]); - let result = ReduceTo::>::reduce_to(&problem); + let result = ReduceTo::>::reduce_to(&problem); let target = result.target_problem(); let graph = target.graph(); - // Exercise all Graph trait methods on the Triangular type + // Exercise all Graph trait methods on the TriangularSubgraph type let n = graph.num_vertices(); assert!(n > 2); @@ -50,10 +50,8 @@ fn test_mis_simple_to_triangular_graph_methods() { } } - // Exercise Triangular-specific methods - let nodes = graph.nodes(); - assert_eq!(nodes.len(), n); - - let inner = graph.grid_graph(); - assert_eq!(inner.num_vertices(), n); + // Exercise TriangularSubgraph-specific methods + let positions = graph.positions(); + assert_eq!(positions.len(), n); + assert_eq!(graph.num_positions(), n); } diff --git a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs index ad5cc1a4..468cb9d7 100644 --- a/src/unit_tests/rules/maximummatching_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximummatching_maximumsetpacking.rs @@ -137,24 +137,44 @@ fn test_jl_parity_matching_to_setpacking() { let match_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/matching.json")).unwrap(); let fixtures: &[(&str, &str)] = &[ - (include_str!("../../../tests/data/jl/matching_to_setpacking.json"), "petersen"), - (include_str!("../../../tests/data/jl/rule_matching_to_setpacking.json"), "rule_4vertex"), - (include_str!("../../../tests/data/jl/rule_matchingw_to_setpacking.json"), "rule_4vertex_weighted"), + ( + include_str!("../../../tests/data/jl/matching_to_setpacking.json"), + "petersen", + ), + ( + include_str!("../../../tests/data/jl/rule_matching_to_setpacking.json"), + "rule_4vertex", + ), + ( + include_str!("../../../tests/data/jl/rule_matchingw_to_setpacking.json"), + "rule_4vertex_weighted", + ), ]; for (fixture_str, label) in fixtures { let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); let inst = &jl_find_instance_by_label(&match_data, label)["instance"]; let source = MaximumMatching::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_weighted_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_weighted_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); - assert!(extracted.is_subset(&best_source), "Matching->SP [{label}]: extracted not subset"); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); + assert!( + extracted.is_subset(&best_source), + "Matching->SP [{label}]: extracted not subset" + ); for case in data["cases"].as_array().unwrap() { - assert_eq!(best_source, jl_parse_configs_set(&case["best_source"]), - "Matching->SP [{label}]: best source mismatch"); + assert_eq!( + best_source, + jl_parse_configs_set(&case["best_source"]), + "Matching->SP [{label}]: best source mismatch" + ); } } } diff --git a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs index e5121865..502c4d63 100644 --- a/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs +++ b/src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs @@ -26,18 +26,25 @@ fn test_reduction_structure() { #[test] fn test_jl_parity_is_to_vertexcovering() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/independentset_to_vertexcovering.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/independentset_to_vertexcovering.json" + )) + .unwrap(); let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &is_data["instances"][0]["instance"]; let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -46,18 +53,25 @@ fn test_jl_parity_is_to_vertexcovering() { #[test] fn test_jl_parity_rule_is_to_vertexcovering() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule2_independentset_to_vertexcovering.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule2_independentset_to_vertexcovering.json" + )) + .unwrap(); let is_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/independentset.json")).unwrap(); let inst = &jl_find_instance_by_label(&is_data, "doc_4vertex")["instance"]; let source = MaximumIndependentSet::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs index 0a8d5143..96879e28 100644 --- a/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs +++ b/src/unit_tests/rules/minimumvertexcover_minimumsetcovering.rs @@ -101,18 +101,26 @@ fn test_vc_to_sc_star_graph() { #[test] fn test_jl_parity_vc_to_setcovering() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering_to_setcovering.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/vertexcovering_to_setcovering.json" + )) + .unwrap(); let vc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); let inst = &vc_data["instances"][0]["instance"]; let source = MinimumVertexCover::with_weights( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst), jl_parse_i32_vec(&inst["weights"])); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + jl_parse_i32_vec(&inst["weights"]), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -121,18 +129,26 @@ fn test_jl_parity_vc_to_setcovering() { #[test] fn test_jl_parity_rule_vc_to_setcovering() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_vertexcovering_to_setcovering.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_vertexcovering_to_setcovering.json" + )) + .unwrap(); let vc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/vertexcovering.json")).unwrap(); let inst = &jl_find_instance_by_label(&vc_data, "rule_4vertex")["instance"]; let source = MinimumVertexCover::with_weights( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_edges(inst), jl_parse_i32_vec(&inst["weights"])); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_edges(inst), + jl_parse_i32_vec(&inst["weights"]), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/natural.rs b/src/unit_tests/rules/natural.rs index c5d1bf9d..924efb40 100644 --- a/src/unit_tests/rules/natural.rs +++ b/src/unit_tests/rules/natural.rs @@ -1,49 +1,45 @@ use crate::models::graph::MaximumIndependentSet; -use crate::rules::{ReduceTo, ReductionResult}; -use crate::solvers::ILPSolver; -use crate::topology::{SimpleGraph, Triangular}; -use crate::traits::Problem; +use crate::rules::graph::{EdgeKind, ReductionGraph}; +use crate::topology::{SimpleGraph, TriangularSubgraph}; +use std::collections::BTreeMap; #[test] -fn test_mis_triangular_to_simple_closed_loop() { - // Petersen graph: 10 vertices, 15 edges, max IS = 4 - let source = MaximumIndependentSet::::new( - 10, - vec![ - (0, 1), (1, 2), (2, 3), (3, 4), (4, 0), // outer cycle - (5, 7), (7, 9), (9, 6), (6, 8), (8, 5), // inner pentagram - (0, 5), (1, 6), (2, 7), (3, 8), (4, 9), // spokes - ], +fn test_natural_cast_triangular_to_simple_via_resolve() { + let graph = ReductionGraph::new(); + + // Find any path from MIS to itself (via VC round-trip) to test natural cast insertion + // Instead, directly test that resolve_path inserts a natural cast for MIS(TriangularSubgraph)->VC(SimpleGraph) + let name_path = graph + .find_shortest_path::< + MaximumIndependentSet, + crate::models::graph::MinimumVertexCover, + >() + .unwrap(); + + let source_variant = BTreeMap::from([ + ("graph".to_string(), "TriangularSubgraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + let target_variant = BTreeMap::from([ + ("graph".to_string(), "SimpleGraph".to_string()), + ("weight".to_string(), "i32".to_string()), + ]); + + let resolved = graph + .resolve_path(&name_path, &source_variant, &target_variant) + .unwrap(); + + // Path should be: MIS(TriangularSubgraph) --NaturalCast--> MIS(SimpleGraph) --Reduction--> VC(SimpleGraph) + assert_eq!(resolved.num_casts(), 1); + assert_eq!(resolved.num_reductions(), 1); + assert!(matches!(resolved.edges[0], EdgeKind::NaturalCast)); + assert!(matches!(resolved.edges[1], EdgeKind::Reduction { .. })); + assert_eq!( + resolved.steps[0].variant.get("graph").unwrap(), + "TriangularSubgraph" + ); + assert_eq!( + resolved.steps[1].variant.get("graph").unwrap(), + "SimpleGraph" ); - - // SimpleGraph → Triangular (unit disk mapping) - let to_tri = ReduceTo::>::reduce_to(&source); - let tri_problem = to_tri.target_problem(); - - // Triangular → SimpleGraph (natural edge: graph subtype relaxation) - let to_simple = ReduceTo::>::reduce_to(tri_problem); - let simple_problem = to_simple.target_problem(); - - // Graph structure is preserved by identity cast - assert_eq!(simple_problem.num_vertices(), tri_problem.num_vertices()); - assert_eq!(simple_problem.num_edges(), tri_problem.num_edges()); - - // Solve with ILP on the relaxed SimpleGraph problem - let solver = ILPSolver::new(); - let solution = solver.solve_reduced(simple_problem).expect("ILP should find a solution"); - - // Identity mapping: solution is unchanged - let extracted = to_simple.extract_solution(&solution); - assert_eq!(extracted, solution); - - // Extracted solution is valid on the Triangular problem - let metric = tri_problem.evaluate(&extracted); - assert!(metric.is_valid()); - - // Map back through the full chain to the original Petersen graph - let original_solution = to_tri.extract_solution(&extracted); - let original_metric = source.evaluate(&original_solution); - assert!(original_metric.is_valid()); - // Petersen graph max IS = 4 - assert_eq!(original_solution.iter().sum::(), 4); } diff --git a/src/unit_tests/rules/sat_coloring.rs b/src/unit_tests/rules/sat_coloring.rs index 6b2d7319..61f56068 100644 --- a/src/unit_tests/rules/sat_coloring.rs +++ b/src/unit_tests/rules/sat_coloring.rs @@ -1,6 +1,7 @@ use super::*; use crate::models::satisfiability::CNFClause; use crate::solvers::BruteForce; +use crate::variant::K3; include!("../jl_helpers.rs"); #[test] @@ -33,7 +34,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::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // Should have 2*1 + 3 = 5 base vertices @@ -50,7 +51,7 @@ fn test_reduction_structure() { vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 2])], ); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // Base vertices: 3 (TRUE, FALSE, AUX) + 2*2 (pos and neg for each var) = 7 @@ -62,12 +63,49 @@ fn test_reduction_structure() { assert_eq!(reduction.neg_vertices().len(), 2); } +#[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 reduction = ReduceTo::>::reduce_to(&sat); + let coloring = reduction.target_problem(); + + // Solve the coloring problem - use find_all_satisfying since KColoring is a satisfaction problem + let solver = BruteForce::new(); + let solutions = solver.find_all_satisfying(coloring); + + // For an unsatisfiable formula, the coloring should have no valid solutions + // OR no valid coloring exists that extracts to a satisfying SAT assignment + let mut found_satisfying = false; + for sol in &solutions { + let sat_sol = reduction.extract_solution(sol); + let assignment: Vec = sat_sol.iter().map(|&v| v == 1).collect(); + if sat.is_satisfying(&assignment) { + found_satisfying = true; + break; + } + } + + // The coloring should not yield a satisfying SAT assignment + // because the formula is unsatisfiable + // Note: The coloring graph itself may still be colorable, + // but the constraints should make it impossible for both + // x1 and NOT x1 to be TRUE color simultaneously + // Actually, let's check if ANY coloring solution produces a valid SAT solution + // If the formula is unsat, no valid coloring should extract to a satisfying assignment + assert!( + !found_satisfying, + "Unsatisfiable formula should not produce satisfying assignment" + ); +} + #[test] 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::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // Base vertices: 3 + 2*3 = 9 @@ -86,7 +124,7 @@ fn test_coloring_structure() { 3, vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])], ); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // Verify coloring has expected structure @@ -98,7 +136,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::>::reduce_to(&sat); + let reduction = ReduceTo::>::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 @@ -130,7 +168,7 @@ fn test_complex_formula_structure() { ], ); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // Base vertices: 3 + 2*3 = 9 @@ -141,11 +179,37 @@ fn test_complex_formula_structure() { assert_eq!(reduction.num_clauses(), 3); } +#[test] +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::>::reduce_to(&sat); + let coloring = reduction.target_problem(); + + let solver = BruteForce::new(); + let solutions = solver.find_all_satisfying(coloring); + + let mut found_correct = false; + for sol in &solutions { + let sat_sol = reduction.extract_solution(sol); + if sat_sol == vec![1, 1] { + found_correct = true; + break; + } + } + + assert!( + found_correct, + "Should find solution where both x1 and x2 are true" + ); +} + #[test] fn test_empty_sat() { // Empty SAT (trivially satisfiable) let sat = Satisfiability::new(0, vec![]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); assert_eq!(reduction.num_clauses(), 0); assert!(reduction.pos_vertices().is_empty()); @@ -162,7 +226,7 @@ fn test_num_clauses_accessor() { 2, vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1])], ); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); assert_eq!(reduction.num_clauses(), 2); } @@ -189,7 +253,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::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let coloring = reduction.target_problem(); // The graph structure for (x1) with set_true: @@ -216,7 +280,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::>::reduce_to(&sat); + let reduction = ReduceTo::>::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) @@ -237,25 +301,41 @@ fn test_jl_parity_sat_to_coloring() { let sat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); let fixtures: &[(&str, &str)] = &[ - (include_str!("../../../tests/data/jl/satisfiability_to_coloring3.json"), "simple_clause"), - (include_str!("../../../tests/data/jl/rule_satisfiability2_to_coloring3.json"), "rule_sat_coloring"), + ( + include_str!("../../../tests/data/jl/satisfiability_to_coloring3.json"), + "simple_clause", + ), + ( + include_str!("../../../tests/data/jl/rule_satisfiability2_to_coloring3.json"), + "rule_sat_coloring", + ), ]; for (fixture_str, label) in fixtures { let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); let inst = &jl_find_instance_by_label(&sat_data, label)["instance"]; let (num_vars, clauses) = jl_parse_sat_clauses(inst); let source = Satisfiability::new(num_vars, clauses); - let result = ReduceTo::>::reduce_to(&source); + let result = ReduceTo::>::reduce_to(&source); let ilp_solver = crate::solvers::ILPSolver::new(); let target = result.target_problem(); - let target_sol = ilp_solver.solve_reduced(target).expect("ILP should find a coloring"); + let target_sol = ilp_solver + .solve_reduced(target) + .expect("ILP should find a coloring"); let extracted = result.extract_solution(&target_sol); let best_source: HashSet> = BruteForce::new() - .find_all_satisfying(&source).into_iter().collect(); - assert!(best_source.contains(&extracted), "SAT->Coloring [{label}]: extracted not satisfying"); + .find_all_satisfying(&source) + .into_iter() + .collect(); + assert!( + best_source.contains(&extracted), + "SAT->Coloring [{label}]: extracted not satisfying" + ); for case in data["cases"].as_array().unwrap() { - assert_eq!(best_source, jl_parse_configs_set(&case["best_source"]), - "SAT->Coloring [{label}]: best source mismatch"); + assert_eq!( + best_source, + jl_parse_configs_set(&case["best_source"]), + "SAT->Coloring [{label}]: best source mismatch" + ); } } } diff --git a/src/unit_tests/rules/sat_ksat.rs b/src/unit_tests/rules/sat_ksat.rs index e08621c8..ea8b5c7f 100644 --- a/src/unit_tests/rules/sat_ksat.rs +++ b/src/unit_tests/rules/sat_ksat.rs @@ -1,5 +1,7 @@ use super::*; use crate::solvers::BruteForce; +use crate::traits::Problem; +use crate::variant::K3; include!("../jl_helpers.rs"); #[test] @@ -7,7 +9,7 @@ fn test_sat_to_3sat_exact_size() { // Clause already has 3 literals - should remain unchanged let sat = Satisfiability::new(3, vec![CNFClause::new(vec![1, 2, 3])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); assert_eq!(ksat.num_vars(), 3); @@ -21,7 +23,7 @@ fn test_sat_to_3sat_padding() { // (a v b) becomes (a v b v x) AND (a v b v -x) let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, 2])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // Should have 2 clauses (positive and negative ancilla) @@ -38,7 +40,7 @@ fn test_sat_to_3sat_splitting() { // (a v b v c v d) becomes (a v b v x) AND (-x v c v d) let sat = Satisfiability::new(4, vec![CNFClause::new(vec![1, 2, 3, 4])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // Should have 2 clauses after splitting @@ -68,7 +70,7 @@ fn test_sat_to_3sat_large_clause() { // (a v b v c v d v e) -> (a v b v x1) AND (-x1 v c v x2) AND (-x2 v d v e) let sat = Satisfiability::new(5, vec![CNFClause::new(vec![1, 2, 3, 4, 5])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // Should have 3 clauses after splitting @@ -85,7 +87,7 @@ fn test_sat_to_3sat_single_literal() { // (a) becomes (a v x v y) where we pad twice let sat = Satisfiability::new(1, vec![CNFClause::new(vec![1])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // With recursive padding: (a) -> (a v x) AND (a v -x) @@ -99,9 +101,66 @@ fn test_sat_to_3sat_single_literal() { } } +#[test] +fn test_sat_to_3sat_preserves_satisfiability() { + // Create a SAT formula and verify the 3-SAT version is equisatisfiable + let sat = Satisfiability::new( + 3, + vec![ + CNFClause::new(vec![1, 2]), // Needs padding + CNFClause::new(vec![-1, 2, 3]), // Already 3 literals + CNFClause::new(vec![1, -2, 3, -3]), // Needs splitting (tautology for testing) + ], + ); + + let reduction = ReduceTo::>::reduce_to(&sat); + let ksat = reduction.target_problem(); + + // Solve both problems - use find_all_satisfying for satisfaction problems + let solver = BruteForce::new(); + + let sat_solutions = solver.find_all_satisfying(&sat); + let ksat_solutions = solver.find_all_satisfying(ksat); + + // If SAT is satisfiable, K-SAT should be too + let sat_satisfiable = !sat_solutions.is_empty(); + let ksat_satisfiable = !ksat_solutions.is_empty(); + + assert_eq!(sat_satisfiable, ksat_satisfiable); + + // Extract solutions should map back correctly + if ksat_satisfiable { + for ksat_sol in &ksat_solutions { + let sat_sol = reduction.extract_solution(ksat_sol); + assert_eq!(sat_sol.len(), 3); // Original variable count + } + } +} + +#[test] +fn test_sat_to_3sat_solution_extraction() { + let sat = Satisfiability::new(2, vec![CNFClause::new(vec![1, 2])]); + + let reduction = ReduceTo::>::reduce_to(&sat); + let ksat = reduction.target_problem(); + + // Solve K-SAT - use find_all_satisfying for satisfaction problems + let solver = BruteForce::new(); + let ksat_solutions = solver.find_all_satisfying(ksat); + + // Extract and verify solutions + for ksat_sol in &ksat_solutions { + let sat_sol = reduction.extract_solution(ksat_sol); + // Should only have original 2 variables + assert_eq!(sat_sol.len(), 2); + // Should satisfy original problem + assert!(sat.evaluate(&sat_sol)); + } +} + #[test] fn test_3sat_to_sat() { - let ksat = KSatisfiability::<3>::new( + let ksat = KSatisfiability::::new( 3, vec![ CNFClause::new(vec![1, 2, 3]), @@ -122,7 +181,7 @@ fn test_3sat_to_sat() { #[test] fn test_3sat_to_sat_solution_extraction() { - let ksat = KSatisfiability::<3>::new(3, vec![CNFClause::new(vec![1, 2, 3])]); + let ksat = KSatisfiability::::new(3, vec![CNFClause::new(vec![1, 2, 3])]); let reduction = ReduceTo::::reduce_to(&ksat); @@ -131,6 +190,35 @@ fn test_3sat_to_sat_solution_extraction() { assert_eq!(extracted, vec![1, 0, 1]); } +#[test] +fn test_roundtrip_sat_3sat_sat() { + // SAT -> 3-SAT -> SAT roundtrip + let original_sat = Satisfiability::new( + 3, + vec![CNFClause::new(vec![1, -2]), CNFClause::new(vec![2, 3])], + ); + + // SAT -> 3-SAT + let to_ksat = ReduceTo::>::reduce_to(&original_sat); + let ksat = to_ksat.target_problem(); + + // 3-SAT -> SAT + let to_sat = ReduceTo::::reduce_to(ksat); + let final_sat = to_sat.target_problem(); + + // Solve all three - use find_all_satisfying for satisfaction problems + let solver = BruteForce::new(); + + let orig_solutions = solver.find_all_satisfying(&original_sat); + let ksat_solutions = solver.find_all_satisfying(ksat); + let final_solutions = solver.find_all_satisfying(final_sat); + + // All should be satisfiable (have at least one solution) + assert!(!orig_solutions.is_empty()); + assert!(!ksat_solutions.is_empty()); + assert!(!final_solutions.is_empty()); +} + #[test] fn test_sat_to_3sat_mixed_clause_types() { // Test padding, exact-size, and splitting all at once @@ -143,7 +231,7 @@ fn test_sat_to_3sat_mixed_clause_types() { ], ); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // All clauses should have exactly 3 literals @@ -156,7 +244,7 @@ fn test_sat_to_3sat_mixed_clause_types() { fn test_ksat_structure() { let sat = Satisfiability::new(3, vec![CNFClause::new(vec![1, 2, 3, 4])]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); // K-SAT should preserve original variables plus auxiliary vars @@ -168,27 +256,82 @@ fn test_ksat_structure() { fn test_empty_sat_to_3sat() { let sat = Satisfiability::new(3, vec![]); - let reduction = ReduceTo::>::reduce_to(&sat); + let reduction = ReduceTo::>::reduce_to(&sat); let ksat = reduction.target_problem(); assert_eq!(ksat.num_clauses(), 0); assert_eq!(ksat.num_vars(), 3); } +#[test] +fn test_mixed_clause_sizes() { + let sat = Satisfiability::new( + 5, + vec![ + CNFClause::new(vec![1]), // 1 literal + CNFClause::new(vec![2, 3]), // 2 literals + CNFClause::new(vec![1, 2, 3]), // 3 literals + CNFClause::new(vec![1, 2, 3, 4]), // 4 literals + CNFClause::new(vec![1, 2, 3, 4, 5]), // 5 literals + ], + ); + + let reduction = ReduceTo::>::reduce_to(&sat); + let ksat = reduction.target_problem(); + + // All clauses should have exactly 3 literals + for clause in ksat.clauses() { + assert_eq!(clause.len(), 3); + } + + // Verify satisfiability is preserved - use find_all_satisfying for satisfaction problems + let solver = BruteForce::new(); + let best_target = solver.find_all_satisfying(ksat); + let best_source: HashSet> = solver.find_all_satisfying(&sat).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| reduction.extract_solution(t)) + .collect(); + assert!(extracted.is_subset(&best_source)); +} + +#[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 reduction = ReduceTo::>::reduce_to(&sat); + let ksat = reduction.target_problem(); + + let solver = BruteForce::new(); + let best_target = solver.find_all_satisfying(ksat); + let best_source: HashSet> = solver.find_all_satisfying(&sat).into_iter().collect(); + + // Both should be empty (unsatisfiable) + assert!(best_source.is_empty()); + assert!(best_target.is_empty()); +} + #[test] fn test_jl_parity_sat_to_ksat() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability_to_ksatisfiability3.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/satisfiability_to_ksatisfiability3.json" + )) + .unwrap(); let sat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); let inst = &sat_data["instances"][0]["instance"]; let (num_vars, clauses) = jl_parse_sat_clauses(inst); let source = Satisfiability::new(num_vars, clauses); - let result = ReduceTo::>::reduce_to(&source); + let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_satisfying(result.target_problem()); - let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let best_source: HashSet> = + solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -197,18 +340,24 @@ fn test_jl_parity_sat_to_ksat() { #[test] fn test_jl_parity_ksat_to_sat() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/ksatisfiability_to_satisfiability.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/ksatisfiability_to_satisfiability.json" + )) + .unwrap(); let ksat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/ksatisfiability.json")).unwrap(); let inst = &ksat_data["instances"][0]["instance"]; let (num_vars, clauses) = jl_parse_sat_clauses(inst); - let source = KSatisfiability::<3>::new(num_vars, clauses); + let source = KSatisfiability::::new(num_vars, clauses); let result = ReduceTo::::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_satisfying(result.target_problem()); - let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let best_source: HashSet> = + solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -217,18 +366,24 @@ fn test_jl_parity_ksat_to_sat() { #[test] fn test_jl_parity_rule_sat_to_ksat() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_satisfiability_to_ksatisfiability3.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_satisfiability_to_ksatisfiability3.json" + )) + .unwrap(); let sat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); let inst = &jl_find_instance_by_label(&sat_data, "rule_3sat_multi")["instance"]; let (num_vars, clauses) = jl_parse_sat_clauses(inst); let source = Satisfiability::new(num_vars, clauses); - let result = ReduceTo::>::reduce_to(&source); + let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_satisfying(result.target_problem()); - let best_source: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let best_source: HashSet> = + solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/sat_maximumindependentset.rs b/src/unit_tests/rules/sat_maximumindependentset.rs index 3d32ce6d..494e590a 100644 --- a/src/unit_tests/rules/sat_maximumindependentset.rs +++ b/src/unit_tests/rules/sat_maximumindependentset.rs @@ -175,12 +175,30 @@ fn test_jl_parity_sat_to_independentset() { let sat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); let fixtures: &[(&str, &str)] = &[ - (include_str!("../../../tests/data/jl/satisfiability_to_independentset.json"), "simple_clause"), - (include_str!("../../../tests/data/jl/rule_sat01_to_independentset.json"), "rule_sat01"), - (include_str!("../../../tests/data/jl/rule_sat02_to_independentset.json"), "rule_sat02"), - (include_str!("../../../tests/data/jl/rule_sat03_to_independentset.json"), "rule_sat03"), - (include_str!("../../../tests/data/jl/rule_sat04_unsat_to_independentset.json"), "rule_sat04_unsat"), - (include_str!("../../../tests/data/jl/rule_sat07_to_independentset.json"), "rule_sat07"), + ( + include_str!("../../../tests/data/jl/satisfiability_to_independentset.json"), + "simple_clause", + ), + ( + include_str!("../../../tests/data/jl/rule_sat01_to_independentset.json"), + "rule_sat01", + ), + ( + include_str!("../../../tests/data/jl/rule_sat02_to_independentset.json"), + "rule_sat02", + ), + ( + include_str!("../../../tests/data/jl/rule_sat03_to_independentset.json"), + "rule_sat03", + ), + ( + include_str!("../../../tests/data/jl/rule_sat04_unsat_to_independentset.json"), + "rule_sat04_unsat", + ), + ( + include_str!("../../../tests/data/jl/rule_sat07_to_independentset.json"), + "rule_sat07", + ), ]; for (fixture_str, label) in fixtures { let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); @@ -190,17 +208,30 @@ fn test_jl_parity_sat_to_independentset() { let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); - let sat_solutions: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); + let sat_solutions: HashSet> = + solver.find_all_satisfying(&source).into_iter().collect(); for case in data["cases"].as_array().unwrap() { if sat_solutions.is_empty() { for sol in &extracted { - assert!(!source.evaluate(sol), "SAT->IS [{label}]: unsatisfiable but extracted satisfies"); + assert!( + !source.evaluate(sol), + "SAT->IS [{label}]: unsatisfiable but extracted satisfies" + ); } } else { - assert!(extracted.is_subset(&sat_solutions), "SAT->IS [{label}]: extracted not subset"); - assert_eq!(sat_solutions, jl_parse_configs_set(&case["best_source"]), - "SAT->IS [{label}]: best source mismatch"); + assert!( + extracted.is_subset(&sat_solutions), + "SAT->IS [{label}]: extracted not subset" + ); + assert_eq!( + sat_solutions, + jl_parse_configs_set(&case["best_source"]), + "SAT->IS [{label}]: best source mismatch" + ); } } } diff --git a/src/unit_tests/rules/sat_minimumdominatingset.rs b/src/unit_tests/rules/sat_minimumdominatingset.rs index aa58799c..fd16b474 100644 --- a/src/unit_tests/rules/sat_minimumdominatingset.rs +++ b/src/unit_tests/rules/sat_minimumdominatingset.rs @@ -163,12 +163,30 @@ fn test_jl_parity_sat_to_dominatingset() { let sat_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/satisfiability.json")).unwrap(); let fixtures: &[(&str, &str)] = &[ - (include_str!("../../../tests/data/jl/satisfiability_to_dominatingset.json"), "simple_clause"), - (include_str!("../../../tests/data/jl/rule_sat01_to_dominatingset.json"), "rule_sat01"), - (include_str!("../../../tests/data/jl/rule_sat02_to_dominatingset.json"), "rule_sat02"), - (include_str!("../../../tests/data/jl/rule_sat03_to_dominatingset.json"), "rule_sat03"), - (include_str!("../../../tests/data/jl/rule_sat04_unsat_to_dominatingset.json"), "rule_sat04_unsat"), - (include_str!("../../../tests/data/jl/rule_sat07_to_dominatingset.json"), "rule_sat07"), + ( + include_str!("../../../tests/data/jl/satisfiability_to_dominatingset.json"), + "simple_clause", + ), + ( + include_str!("../../../tests/data/jl/rule_sat01_to_dominatingset.json"), + "rule_sat01", + ), + ( + include_str!("../../../tests/data/jl/rule_sat02_to_dominatingset.json"), + "rule_sat02", + ), + ( + include_str!("../../../tests/data/jl/rule_sat03_to_dominatingset.json"), + "rule_sat03", + ), + ( + include_str!("../../../tests/data/jl/rule_sat04_unsat_to_dominatingset.json"), + "rule_sat04_unsat", + ), + ( + include_str!("../../../tests/data/jl/rule_sat07_to_dominatingset.json"), + "rule_sat07", + ), ]; for (fixture_str, label) in fixtures { let data: serde_json::Value = serde_json::from_str(fixture_str).unwrap(); @@ -178,17 +196,30 @@ fn test_jl_parity_sat_to_dominatingset() { let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); - let sat_solutions: HashSet> = solver.find_all_satisfying(&source).into_iter().collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); + let sat_solutions: HashSet> = + solver.find_all_satisfying(&source).into_iter().collect(); for case in data["cases"].as_array().unwrap() { if sat_solutions.is_empty() { for sol in &extracted { - assert!(!source.evaluate(sol), "SAT->DS [{label}]: unsatisfiable but extracted satisfies"); + assert!( + !source.evaluate(sol), + "SAT->DS [{label}]: unsatisfiable but extracted satisfies" + ); } } else { - assert!(extracted.is_subset(&sat_solutions), "SAT->DS [{label}]: extracted not subset"); - assert_eq!(sat_solutions, jl_parse_configs_set(&case["best_source"]), - "SAT->DS [{label}]: best source mismatch"); + assert!( + extracted.is_subset(&sat_solutions), + "SAT->DS [{label}]: extracted not subset" + ); + assert_eq!( + sat_solutions, + jl_parse_configs_set(&case["best_source"]), + "SAT->DS [{label}]: best source mismatch" + ); } } } diff --git a/src/unit_tests/rules/spinglass_maxcut.rs b/src/unit_tests/rules/spinglass_maxcut.rs index 9171a21e..efd71270 100644 --- a/src/unit_tests/rules/spinglass_maxcut.rs +++ b/src/unit_tests/rules/spinglass_maxcut.rs @@ -81,8 +81,10 @@ fn test_reduction_structure() { #[test] fn test_jl_parity_spinglass_to_maxcut() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/spinglass_to_maxcut.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/spinglass_to_maxcut.json" + )) + .unwrap(); let sg_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); let inst = &sg_data["instances"][0]["instance"]; @@ -96,7 +98,10 @@ fn test_jl_parity_spinglass_to_maxcut() { let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -105,8 +110,10 @@ fn test_jl_parity_spinglass_to_maxcut() { #[test] fn test_jl_parity_maxcut_to_spinglass() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/maxcut_to_spinglass.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/maxcut_to_spinglass.json" + )) + .unwrap(); let mc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/maxcut.json")).unwrap(); let inst = &mc_data["instances"][0]["instance"]; @@ -117,7 +124,10 @@ fn test_jl_parity_maxcut_to_spinglass() { let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -126,18 +136,25 @@ fn test_jl_parity_maxcut_to_spinglass() { #[test] fn test_jl_parity_rule_maxcut_to_spinglass() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_maxcut_to_spinglass.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_maxcut_to_spinglass.json" + )) + .unwrap(); let mc_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/maxcut.json")).unwrap(); let inst = &jl_find_instance_by_label(&mc_data, "rule_4vertex")["instance"]; let source = MaxCut::::new( - inst["num_vertices"].as_u64().unwrap() as usize, jl_parse_weighted_edges(inst)); + inst["num_vertices"].as_u64().unwrap() as usize, + jl_parse_weighted_edges(inst), + ); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -146,8 +163,10 @@ fn test_jl_parity_rule_maxcut_to_spinglass() { #[test] fn test_jl_parity_rule_spinglass_to_maxcut() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_spinglass_to_maxcut.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_spinglass_to_maxcut.json" + )) + .unwrap(); let sg_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); let inst = &jl_find_instance_by_label(&sg_data, "rule_4vertex")["instance"]; @@ -161,7 +180,10 @@ fn test_jl_parity_rule_spinglass_to_maxcut() { let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/spinglass_qubo.rs b/src/unit_tests/rules/spinglass_qubo.rs index 235cc48f..7ffe80c4 100644 --- a/src/unit_tests/rules/spinglass_qubo.rs +++ b/src/unit_tests/rules/spinglass_qubo.rs @@ -58,22 +58,37 @@ fn test_reduction_structure() { #[test] fn test_jl_parity_spinglass_to_qubo() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/spinglass_to_qubo.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/spinglass_to_qubo.json" + )) + .unwrap(); let sg_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/spinglass.json")).unwrap(); let inst = &sg_data["instances"][0]["instance"]; let nv = inst["num_vertices"].as_u64().unwrap() as usize; let edges = jl_parse_edges(inst); - let j_values: Vec = inst["J"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect(); - let h_values: Vec = inst["h"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect(); + let j_values: Vec = inst["J"] + .as_array() + .unwrap() + .iter() + .map(|v| v.as_i64().unwrap() as f64) + .collect(); + let h_values: Vec = inst["h"] + .as_array() + .unwrap() + .iter() + .map(|v| v.as_i64().unwrap() as f64) + .collect(); let interactions: Vec<((usize, usize), f64)> = edges.into_iter().zip(j_values).collect(); let source = SpinGlass::::new(nv, interactions, h_values); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -82,26 +97,41 @@ fn test_jl_parity_spinglass_to_qubo() { #[test] fn test_jl_parity_qubo_to_spinglass() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/qubo_to_spinglass.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/qubo_to_spinglass.json" + )) + .unwrap(); let q_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/qubo.json")).unwrap(); let jl_matrix: Vec> = q_data["instances"][0]["instance"]["matrix"] - .as_array().unwrap().iter() - .map(|row| row.as_array().unwrap().iter().map(|v| v.as_i64().unwrap() as f64).collect()) + .as_array() + .unwrap() + .iter() + .map(|row| { + row.as_array() + .unwrap() + .iter() + .map(|v| v.as_i64().unwrap() as f64) + .collect() + }) .collect(); let n = jl_matrix.len(); let mut rust_matrix = vec![vec![0.0f64; n]; n]; for i in 0..n { rust_matrix[i][i] = jl_matrix[i][i]; - for j in (i + 1)..n { rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; } + for j in (i + 1)..n { + rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; + } } let source = QUBO::from_matrix(rust_matrix); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); @@ -110,26 +140,42 @@ fn test_jl_parity_qubo_to_spinglass() { #[test] fn test_jl_parity_rule_qubo_to_spinglass() { - let data: serde_json::Value = - serde_json::from_str(include_str!("../../../tests/data/jl/rule_qubo_to_spinglass.json")).unwrap(); + let data: serde_json::Value = serde_json::from_str(include_str!( + "../../../tests/data/jl/rule_qubo_to_spinglass.json" + )) + .unwrap(); let q_data: serde_json::Value = serde_json::from_str(include_str!("../../../tests/data/jl/qubo.json")).unwrap(); - let jl_matrix: Vec> = jl_find_instance_by_label(&q_data, "rule_3x3")["instance"]["matrix"] - .as_array().unwrap().iter() - .map(|row| row.as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect()) + let jl_matrix: Vec> = jl_find_instance_by_label(&q_data, "rule_3x3")["instance"] + ["matrix"] + .as_array() + .unwrap() + .iter() + .map(|row| { + row.as_array() + .unwrap() + .iter() + .map(|v| v.as_f64().unwrap()) + .collect() + }) .collect(); let n = jl_matrix.len(); let mut rust_matrix = vec![vec![0.0f64; n]; n]; for i in 0..n { rust_matrix[i][i] = jl_matrix[i][i]; - for j in (i + 1)..n { rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; } + for j in (i + 1)..n { + rust_matrix[i][j] = jl_matrix[i][j] + jl_matrix[j][i]; + } } let source = QUBO::from_matrix(rust_matrix); let result = ReduceTo::>::reduce_to(&source); let solver = BruteForce::new(); let best_target = solver.find_all_best(result.target_problem()); let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); - let extracted: HashSet> = best_target.iter().map(|t| result.extract_solution(t)).collect(); + let extracted: HashSet> = best_target + .iter() + .map(|t| result.extract_solution(t)) + .collect(); assert!(extracted.is_subset(&best_source)); for case in data["cases"].as_array().unwrap() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); diff --git a/src/unit_tests/rules/unitdiskmapping/ksg/mapping.rs b/src/unit_tests/rules/unitdiskmapping/ksg/mapping.rs index e1168870..3ff87611 100644 --- a/src/unit_tests/rules/unitdiskmapping/ksg/mapping.rs +++ b/src/unit_tests/rules/unitdiskmapping/ksg/mapping.rs @@ -1,5 +1,4 @@ use super::*; -use crate::topology::Graph; #[test] fn test_embed_graph_path() { @@ -18,7 +17,7 @@ fn test_map_unweighted_triangle() { let edges = vec![(0, 1), (1, 2), (0, 2)]; let result = map_unweighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); // mis_overhead can be negative due to gadgets, so we just verify the function completes } @@ -28,7 +27,7 @@ fn test_map_weighted_triangle() { let edges = vec![(0, 1), (1, 2), (0, 2)]; let result = map_weighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] @@ -37,7 +36,7 @@ fn test_mapping_result_config_back_unweighted() { let result = map_unweighted(2, &edges); // Create a dummy config - let config: Vec = vec![0; result.grid_graph.num_vertices()]; + let config: Vec = vec![0; result.positions.len()]; let original = result.map_config_back(&config); assert_eq!(original.len(), 2); @@ -49,7 +48,7 @@ fn test_mapping_result_config_back_weighted() { let result = map_weighted(2, &edges); // Create a dummy config - let config: Vec = vec![0; result.grid_graph.num_vertices()]; + let config: Vec = vec![0; result.positions.len()]; let original = result.map_config_back(&config); assert_eq!(original.len(), 2); @@ -89,7 +88,7 @@ fn test_map_unweighted_with_method() { let edges = vec![(0, 1), (1, 2)]; let result = map_unweighted_with_method(3, &edges, PathDecompositionMethod::greedy()); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] @@ -97,5 +96,5 @@ fn test_map_weighted_with_method() { let edges = vec![(0, 1), (1, 2)]; let result = map_weighted_with_method(3, &edges, PathDecompositionMethod::greedy()); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } diff --git a/src/unit_tests/rules/unitdiskmapping/triangular/mapping.rs b/src/unit_tests/rules/unitdiskmapping/triangular/mapping.rs index 0374425f..a9508688 100644 --- a/src/unit_tests/rules/unitdiskmapping/triangular/mapping.rs +++ b/src/unit_tests/rules/unitdiskmapping/triangular/mapping.rs @@ -1,15 +1,14 @@ use super::*; -use crate::topology::Graph; #[test] fn test_map_weighted_basic() { let edges = vec![(0, 1), (1, 2)]; let result = map_weighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert!(matches!( - result.grid_graph.grid_type(), - GridType::Triangular { .. } + result.kind, + GridKind::Triangular )); } @@ -18,7 +17,7 @@ fn test_map_weighted_with_method() { let edges = vec![(0, 1), (1, 2)]; let result = map_weighted_with_method(3, &edges, PathDecompositionMethod::MinhThiTrick); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] @@ -27,7 +26,7 @@ fn test_map_weighted_with_order() { let vertex_order = vec![0, 1, 2]; let result = map_weighted_with_order(3, &edges, &vertex_order); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] @@ -54,7 +53,7 @@ fn test_map_weights() { let grid_weights = map_weights(&result, &source_weights); // Should have same length as grid nodes - assert_eq!(grid_weights.len(), result.grid_graph.num_vertices()); + assert_eq!(grid_weights.len(), result.positions.len()); // All weights should be positive assert!(grid_weights.iter().all(|&w| w > 0.0)); diff --git a/src/unit_tests/rules/unitdiskmapping/triangular/mod.rs b/src/unit_tests/rules/unitdiskmapping/triangular/mod.rs index 4937b7d4..62b286b6 100644 --- a/src/unit_tests/rules/unitdiskmapping/triangular/mod.rs +++ b/src/unit_tests/rules/unitdiskmapping/triangular/mod.rs @@ -1,5 +1,4 @@ use super::*; -use crate::topology::Graph; #[test] fn test_triangular_cross_gadget() { @@ -13,10 +12,10 @@ fn test_map_graph_triangular() { let edges = vec![(0, 1), (1, 2)]; let result = map_graph_triangular(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert!(matches!( - result.grid_graph.grid_type(), - GridType::Triangular { .. } + result.kind, + GridKind::Triangular )); } @@ -66,7 +65,7 @@ fn test_map_graph_triangular_with_order() { let order = vec![2, 1, 0]; let result = map_graph_triangular_with_order(3, &edges, &order); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert_eq!(result.spacing, TRIANGULAR_SPACING); assert_eq!(result.padding, TRIANGULAR_PADDING); } @@ -76,7 +75,7 @@ fn test_map_graph_triangular_single_vertex() { let edges: Vec<(usize, usize)> = vec![]; let result = map_graph_triangular(1, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] diff --git a/src/unit_tests/rules/unitdiskmapping/weighted.rs b/src/unit_tests/rules/unitdiskmapping/weighted.rs index e8523f0a..4126fe72 100644 --- a/src/unit_tests/rules/unitdiskmapping/weighted.rs +++ b/src/unit_tests/rules/unitdiskmapping/weighted.rs @@ -85,10 +85,10 @@ fn test_triangular_weighted_ruleset_has_13_gadgets() { #[test] fn test_trace_centers_basic() { - use crate::rules::unitdiskmapping::map_graph_triangular; + use crate::rules::unitdiskmapping::triangular::map_weighted; let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = map_weighted(3, &edges); let centers = super::trace_centers(&result); assert_eq!(centers.len(), 3); @@ -102,17 +102,15 @@ fn test_trace_centers_basic() { #[test] fn test_map_weights_basic() { - use crate::rules::unitdiskmapping::map_graph_triangular; - use crate::topology::Graph; - + use crate::rules::unitdiskmapping::triangular::map_weighted; let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = map_weighted(3, &edges); let source_weights = vec![0.5, 0.3, 0.7]; let grid_weights = super::map_weights(&result, &source_weights); // Should have same length as grid nodes - assert_eq!(grid_weights.len(), result.grid_graph.num_vertices()); + assert_eq!(grid_weights.len(), result.positions.len()); // All weights should be positive assert!(grid_weights.iter().all(|&w| w > 0.0)); @@ -121,10 +119,10 @@ fn test_map_weights_basic() { #[test] #[should_panic(expected = "all weights must be in range")] fn test_map_weights_rejects_invalid() { - use crate::rules::unitdiskmapping::map_graph_triangular; + use crate::rules::unitdiskmapping::triangular::map_weighted; let edges = vec![(0, 1)]; - let result = map_graph_triangular(2, &edges); + let result = map_weighted(2, &edges); let source_weights = vec![1.5, 0.3]; // Invalid: > 1 super::map_weights(&result, &source_weights); diff --git a/src/unit_tests/topology/grid_graph.rs b/src/unit_tests/topology/grid_graph.rs deleted file mode 100644 index b6383d49..00000000 --- a/src/unit_tests/topology/grid_graph.rs +++ /dev/null @@ -1,224 +0,0 @@ -use super::*; - -#[test] -fn test_grid_graph_square_basic() { - let nodes = vec![ - GridNode::new(0, 0, 1), - GridNode::new(1, 0, 1), - GridNode::new(0, 1, 1), - ]; - // With radius 1.1: (0,0)-(1,0) dist=1.0 < 1.1, (0,0)-(0,1) dist=1.0 < 1.1, (1,0)-(0,1) dist=sqrt(2)>1.1 - // Using dist < radius (strict), so edges at exactly 1.0 are included with radius 1.1 - let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 1.1); - assert_eq!(grid.num_vertices(), 3); - // Only nodes at (0,0)-(1,0) and (0,0)-(0,1) are within radius 1.1 - assert_eq!(grid.edges().len(), 2); -} - -#[test] -fn test_grid_graph_triangular_basic() { - let nodes = vec![ - GridNode::new(0, 0, 1), - GridNode::new(1, 0, 1), - GridNode::new(0, 1, 1), - ]; - let grid = GridGraph::new( - GridType::Triangular { - offset_even_cols: false, - }, - (2, 2), - nodes, - 1.1, - ); - assert_eq!(grid.num_vertices(), 3); -} - -#[test] -fn test_grid_node_new() { - let node: GridNode = GridNode::new(5, 10, 42); - assert_eq!(node.row, 5); - assert_eq!(node.col, 10); - assert_eq!(node.weight, 42); -} - -#[test] -fn test_grid_graph_square_physical_position() { - let nodes = vec![GridNode::new(3, 4, 1)]; - let grid = GridGraph::new(GridType::Square, (10, 10), nodes, 1.0); - let pos = grid.physical_position(3, 4); - assert_eq!(pos, (3.0, 4.0)); -} - -#[test] -fn test_grid_graph_triangular_physical_position() { - let nodes = vec![GridNode::new(0, 0, 1)]; - let grid = GridGraph::new( - GridType::Triangular { - offset_even_cols: false, - }, - (10, 10), - nodes, - 1.0, - ); - - // Col 0 (even), offset_even_cols = false -> no offset - let pos0 = grid.physical_position(0, 0); - assert!((pos0.0 - 0.0).abs() < 1e-10); - assert!((pos0.1 - 0.0).abs() < 1e-10); - - // Col 1 (odd), offset_even_cols = false -> offset 0.5 - let pos1 = grid.physical_position(0, 1); - assert!((pos1.0 - 0.5).abs() < 1e-10); - assert!((pos1.1 - (3.0_f64.sqrt() / 2.0)).abs() < 1e-10); -} - -#[test] -fn test_grid_graph_triangular_offset_even() { - let nodes = vec![GridNode::new(0, 0, 1)]; - let grid = GridGraph::new( - GridType::Triangular { - offset_even_cols: true, - }, - (10, 10), - nodes, - 1.0, - ); - - // Col 0 (even), offset_even_cols = true -> offset 0.5 - let pos0 = grid.physical_position(0, 0); - assert!((pos0.0 - 0.5).abs() < 1e-10); - - // Col 1 (odd), offset_even_cols = true -> no offset - let pos1 = grid.physical_position(0, 1); - assert!((pos1.0 - 0.0).abs() < 1e-10); -} - -#[test] -fn test_grid_graph_edges_within_radius() { - // Square grid: place nodes at (0,0), (1,0), (2,0) - // Distance (0,0)-(1,0) = 1.0 - // Distance (0,0)-(2,0) = 2.0 - // Distance (1,0)-(2,0) = 1.0 - let nodes = vec![ - GridNode::new(0, 0, 1), - GridNode::new(1, 0, 1), - GridNode::new(2, 0, 1), - ]; - // Use radius 1.1 since edges are created for dist < radius (strict) - // With radius 1.0, no edges at exact distance 1.0 - // With radius 1.1, edges at distance 1.0 are included - let grid = GridGraph::new(GridType::Square, (3, 1), nodes, 1.1); - - // Only edges within radius 1.1: (0,1) and (1,2) with dist=1.0 - assert_eq!(grid.num_edges(), 2); - assert!(grid.has_edge(0, 1)); - assert!(grid.has_edge(1, 2)); - assert!(!grid.has_edge(0, 2)); // dist=2.0 >= 1.1 -} - -#[test] -fn test_grid_graph_neighbors() { - let nodes = vec![ - GridNode::new(0, 0, 1), - GridNode::new(1, 0, 1), - GridNode::new(0, 1, 1), - ]; - let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 1.5); - - let neighbors_0 = grid.neighbors(0); - assert_eq!(neighbors_0.len(), 2); - assert!(neighbors_0.contains(&1)); - assert!(neighbors_0.contains(&2)); -} - -#[test] -fn test_grid_graph_accessors() { - let nodes = vec![GridNode::new(0, 0, 10), GridNode::new(1, 0, 20)]; - let grid = GridGraph::new(GridType::Square, (5, 5), nodes, 2.0); - - assert_eq!(grid.grid_type(), GridType::Square); - assert_eq!(grid.size(), (5, 5)); - assert_eq!(grid.radius(), 2.0); - assert_eq!(grid.nodes().len(), 2); - assert_eq!(grid.node(0).map(|n| n.weight), Some(10)); - assert_eq!(grid.weight(1), Some(&20)); - assert_eq!(grid.weight(5), None); -} - -#[test] -fn test_grid_graph_node_position() { - let nodes = vec![GridNode::new(2, 3, 1)]; - let grid = GridGraph::new(GridType::Square, (10, 10), nodes, 1.0); - - let pos = grid.node_position(0); - assert_eq!(pos, Some((2.0, 3.0))); - assert_eq!(grid.node_position(1), None); -} - -#[test] -fn test_grid_graph_has_edge_symmetric() { - let nodes = vec![GridNode::new(0, 0, 1), GridNode::new(1, 0, 1)]; - let grid = GridGraph::new(GridType::Square, (2, 1), nodes, 1.5); - - assert!(grid.has_edge(0, 1)); - assert!(grid.has_edge(1, 0)); // Symmetric -} - -#[test] -fn test_grid_graph_empty() { - let nodes: Vec> = vec![]; - let grid = GridGraph::new(GridType::Square, (0, 0), nodes, 1.0); - - assert_eq!(grid.num_vertices(), 0); - assert_eq!(grid.num_edges(), 0); - assert!(grid.is_empty()); -} - -#[test] -fn test_grid_graph_graph_trait() { - let nodes = vec![ - GridNode::new(0, 0, 1), - GridNode::new(1, 0, 1), - GridNode::new(0, 1, 1), - ]; - // With radius 1.1: 2 edges at dist=1.0 (not including diagonal at sqrt(2)>1.1) - // Using dist < radius (strict), so edges at exactly 1.0 are included with radius 1.1 - let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 1.1); - - // Test Graph trait methods - assert_eq!(Graph::num_vertices(&grid), 3); - assert_eq!(Graph::num_edges(&grid), 2); - assert_eq!(grid.degree(0), 2); - assert_eq!(grid.degree(1), 1); - assert_eq!(grid.degree(2), 1); -} - -#[test] -fn test_grid_graph_display() { - let nodes = vec![GridNode::new(0, 0, 1), GridNode::new(1, 0, 2)]; - let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 2.0); - - // Test Display trait - let display_str = format!("{}", grid); - assert!(!display_str.is_empty()); -} - -#[test] -fn test_grid_graph_format_empty() { - let nodes: Vec> = vec![]; - let grid = GridGraph::new(GridType::Square, (0, 0), nodes, 1.0); - - // Empty grid should return "(empty grid graph)" - let formatted = grid.format_with_config(None, false); - assert_eq!(formatted, "(empty grid graph)"); -} - -#[test] -fn test_grid_graph_format_with_config() { - let nodes = vec![GridNode::new(0, 0, 1), GridNode::new(1, 0, 1)]; - let grid = GridGraph::new(GridType::Square, (2, 2), nodes, 2.0); - - // Test format with config - let formatted = grid.format_with_config(Some(&[1, 0]), false); - assert!(!formatted.is_empty()); -} diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index df3452a2..f85f457b 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -5,6 +5,7 @@ use crate::models::set::*; use crate::models::specialized::*; use crate::topology::SimpleGraph; use crate::traits::Problem; +use crate::variant::K3; fn check_problem_trait(problem: &P, name: &str) { let dims = problem.dims(); @@ -37,7 +38,7 @@ fn test_all_problems_implement_trait_correctly() { "MaxCut", ); check_problem_trait( - &KColoring::<3, SimpleGraph>::new(3, vec![(0, 1)]), + &KColoring::::new(3, vec![(0, 1)]), "KColoring", ); check_problem_trait( diff --git a/src/unit_tests/unitdiskmapping_algorithms/common.rs b/src/unit_tests/unitdiskmapping_algorithms/common.rs index 473eeee5..7a3a2948 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/common.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/common.rs @@ -5,7 +5,7 @@ use crate::models::MaximumIndependentSet; use crate::rules::unitdiskmapping::MappingResult; use crate::rules::{ReduceTo, ReductionResult}; use crate::solvers::ILPSolver; -use crate::topology::{Graph, SimpleGraph}; +use crate::topology::SimpleGraph; /// Check if a configuration is a valid independent set. pub fn is_independent_set(edges: &[(usize, usize)], config: &[usize]) -> bool { @@ -45,22 +45,22 @@ pub fn solve_mis_config(num_vertices: usize, edges: &[(usize, usize)]) -> Vec usize { - let edges = result.grid_graph.edges().to_vec(); - let num_vertices = result.grid_graph.num_vertices(); + let edges = result.edges(); + let num_vertices = result.positions.len(); solve_mis(num_vertices, &edges) } -/// Solve weighted MIS on a GridGraph using ILPSolver. +/// Solve weighted MIS on a Grid using ILPSolver. #[allow(dead_code)] pub fn solve_weighted_grid_mis(result: &MappingResult) -> usize { - let edges = result.grid_graph.edges().to_vec(); - let num_vertices = result.grid_graph.num_vertices(); + let edges = result.edges(); + let num_vertices = result.positions.len(); let weights: Vec = (0..num_vertices) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); solve_weighted_mis(num_vertices, &edges, &weights) as usize diff --git a/src/unit_tests/unitdiskmapping_algorithms/copyline.rs b/src/unit_tests/unitdiskmapping_algorithms/copyline.rs index a8581bd9..e916fc14 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/copyline.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/copyline.rs @@ -2,7 +2,7 @@ use super::common::solve_weighted_mis; use crate::rules::unitdiskmapping::{ - create_copylines, map_graph, map_graph_triangular, mis_overhead_copyline, CopyLine, + create_copylines, ksg, mis_overhead_copyline, triangular, CopyLine, }; // === Edge Case Tests === @@ -46,7 +46,7 @@ fn test_mis_overhead_copyline_zero_hstop() { #[test] fn test_copylines_have_valid_vertex_ids() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); for line in &result.lines { assert!(line.vertex < 3, "Vertex ID should be in range"); @@ -56,7 +56,7 @@ fn test_copylines_have_valid_vertex_ids() { #[test] fn test_copylines_have_positive_slots() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); for line in &result.lines { assert!(line.vslot > 0, "vslot should be positive"); @@ -67,7 +67,7 @@ fn test_copylines_have_positive_slots() { #[test] fn test_copylines_have_valid_ranges() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); for line in &result.lines { assert!(line.vstart <= line.vstop, "vstart should be <= vstop"); @@ -145,7 +145,7 @@ fn test_copyline_copyline_locations_triangular() { #[test] fn test_mapping_result_has_copylines() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); assert_eq!(result.lines.len(), 3); @@ -160,7 +160,7 @@ fn test_mapping_result_has_copylines() { #[test] fn test_triangular_mapping_result_has_copylines() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); assert_eq!(result.lines.len(), 3); } @@ -168,7 +168,7 @@ fn test_triangular_mapping_result_has_copylines() { #[test] fn test_copyline_vslot_hslot_ordering() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); // vslot is determined by vertex order, should be 1-indexed let mut vslots: Vec = result.lines.iter().map(|l| l.vslot).collect(); @@ -183,7 +183,7 @@ fn test_copyline_vslot_hslot_ordering() { #[test] fn test_copyline_center_on_grid() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); // Each copyline's center should correspond to a grid node for line in &result.lines { @@ -255,7 +255,7 @@ fn test_copyline_copyline_locations_structure() { #[test] fn test_copyline_triangular_spacing() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); // Triangular uses spacing=6 assert_eq!(result.spacing, 6); diff --git a/src/unit_tests/unitdiskmapping_algorithms/gadgets.rs b/src/unit_tests/unitdiskmapping_algorithms/gadgets.rs index e048c894..178544be 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/gadgets.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/gadgets.rs @@ -1,20 +1,25 @@ //! Tests for gadget properties (src/rules/mapping/gadgets.rs and triangular gadgets). use super::common::{solve_weighted_mis, triangular_edges}; -use crate::rules::unitdiskmapping::{ - Branch, BranchFix, Cross, EndTurn, Mirror, Pattern, ReflectedGadget, RotatedGadget, TCon, - TriBranch, TriBranchFix, TriBranchFixB, TriCross, TriEndTurn, TriTConDown, TriTConUp, - TriTrivialTurnLeft, TriTrivialTurnRight, TriTurn, TriWTurn, TriangularGadget, TrivialTurn, - Turn, WTurn, WeightedKsgBranch, WeightedKsgBranchFix, WeightedKsgBranchFixB, WeightedKsgCross, +use crate::rules::unitdiskmapping::ksg::{ + KsgBranch, KsgBranchFix, KsgBranchFixB, KsgCross, KsgDanglingLeg, KsgEndTurn, + KsgReflectedGadget, KsgRotatedGadget, KsgTCon, KsgTrivialTurn, KsgTurn, KsgWTurn, Mirror, + WeightedKsgBranch, WeightedKsgBranchFix, WeightedKsgBranchFixB, WeightedKsgCross, WeightedKsgDanglingLeg, WeightedKsgEndTurn, WeightedKsgTCon, WeightedKsgTrivialTurn, WeightedKsgTurn, WeightedKsgWTurn, }; +use crate::rules::unitdiskmapping::triangular::{ + WeightedTriBranch, WeightedTriBranchFix, WeightedTriBranchFixB, WeightedTriCross, + WeightedTriEndTurn, WeightedTriTConDown, WeightedTriTConUp, WeightedTriTrivialTurnLeft, + WeightedTriTrivialTurnRight, WeightedTriTurn, WeightedTriWTurn, WeightedTriangularGadget, +}; +use crate::rules::unitdiskmapping::Pattern; // === Square Gadget Tests === #[test] fn test_cross_disconnected_gadget() { - let gadget = Cross::; + let gadget = KsgCross::; let (locs, edges, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -24,7 +29,7 @@ fn test_cross_disconnected_gadget() { #[test] fn test_cross_connected_gadget() { - let gadget = Cross::; + let gadget = KsgCross::; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -33,7 +38,7 @@ fn test_cross_connected_gadget() { #[test] fn test_turn_gadget() { - let gadget = Turn; + let gadget = KsgTurn; let (locs, edges, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -43,7 +48,7 @@ fn test_turn_gadget() { #[test] fn test_wturn_gadget() { - let gadget = WTurn; + let gadget = KsgWTurn; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -52,7 +57,7 @@ fn test_wturn_gadget() { #[test] fn test_branch_gadget() { - let gadget = Branch; + let gadget = KsgBranch; let (locs, edges, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -62,7 +67,7 @@ fn test_branch_gadget() { #[test] fn test_branch_fix_gadget() { - let gadget = BranchFix; + let gadget = KsgBranchFix; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -71,7 +76,7 @@ fn test_branch_fix_gadget() { #[test] fn test_tcon_gadget() { - let gadget = TCon; + let gadget = KsgTCon; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -80,7 +85,7 @@ fn test_tcon_gadget() { #[test] fn test_trivial_turn_gadget() { - let gadget = TrivialTurn; + let gadget = KsgTrivialTurn; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -89,7 +94,7 @@ fn test_trivial_turn_gadget() { #[test] fn test_end_turn_gadget() { - let gadget = EndTurn; + let gadget = KsgEndTurn; let (locs, _, pins) = gadget.source_graph(); assert!(!locs.is_empty()); @@ -99,64 +104,64 @@ fn test_end_turn_gadget() { #[test] fn test_all_gadgets_have_valid_pins() { // Test Cross - let (source_locs, _, source_pins) = Cross::.source_graph(); - let (mapped_locs, mapped_pins) = Cross::.mapped_graph(); + let (source_locs, _, source_pins) = KsgCross::.source_graph(); + let (mapped_locs, mapped_pins) = KsgCross::.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test Cross - let (source_locs, _, source_pins) = Cross::.source_graph(); - let (mapped_locs, mapped_pins) = Cross::.mapped_graph(); + let (source_locs, _, source_pins) = KsgCross::.source_graph(); + let (mapped_locs, mapped_pins) = KsgCross::.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test Turn - let (source_locs, _, source_pins) = Turn.source_graph(); - let (mapped_locs, mapped_pins) = Turn.mapped_graph(); + let (source_locs, _, source_pins) = KsgTurn.source_graph(); + let (mapped_locs, mapped_pins) = KsgTurn.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test WTurn - let (source_locs, _, source_pins) = WTurn.source_graph(); - let (mapped_locs, mapped_pins) = WTurn.mapped_graph(); + let (source_locs, _, source_pins) = KsgWTurn.source_graph(); + let (mapped_locs, mapped_pins) = KsgWTurn.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test Branch - let (source_locs, _, source_pins) = Branch.source_graph(); - let (mapped_locs, mapped_pins) = Branch.mapped_graph(); + let (source_locs, _, source_pins) = KsgBranch.source_graph(); + let (mapped_locs, mapped_pins) = KsgBranch.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test BranchFix - let (source_locs, _, source_pins) = BranchFix.source_graph(); - let (mapped_locs, mapped_pins) = BranchFix.mapped_graph(); + let (source_locs, _, source_pins) = KsgBranchFix.source_graph(); + let (mapped_locs, mapped_pins) = KsgBranchFix.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test TCon - let (source_locs, _, source_pins) = TCon.source_graph(); - let (mapped_locs, mapped_pins) = TCon.mapped_graph(); + let (source_locs, _, source_pins) = KsgTCon.source_graph(); + let (mapped_locs, mapped_pins) = KsgTCon.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test TrivialTurn - let (source_locs, _, source_pins) = TrivialTurn.source_graph(); - let (mapped_locs, mapped_pins) = TrivialTurn.mapped_graph(); + let (source_locs, _, source_pins) = KsgTrivialTurn.source_graph(); + let (mapped_locs, mapped_pins) = KsgTrivialTurn.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); // Test EndTurn - let (source_locs, _, source_pins) = EndTurn.source_graph(); - let (mapped_locs, mapped_pins) = EndTurn.mapped_graph(); + let (source_locs, _, source_pins) = KsgEndTurn.source_graph(); + let (mapped_locs, mapped_pins) = KsgEndTurn.mapped_graph(); assert!(source_pins.iter().all(|&p| p < source_locs.len())); assert!(mapped_pins.iter().all(|&p| p < mapped_locs.len())); assert_eq!(source_pins.len(), mapped_pins.len()); @@ -166,7 +171,7 @@ fn test_all_gadgets_have_valid_pins() { #[test] fn test_triangular_gadgets_have_valid_pins() { - fn check_tri_gadget(gadget: G, name: &str) { + fn check_tri_gadget(gadget: G, name: &str) { let (source_locs, _, source_pins) = gadget.source_graph(); let (mapped_locs, mapped_pins) = gadget.mapped_graph(); @@ -182,26 +187,26 @@ fn test_triangular_gadgets_have_valid_pins() { ); } - check_tri_gadget(TriCross::, "TriCross"); - check_tri_gadget(TriCross::, "TriCross"); - check_tri_gadget(TriTurn, "TriTurn"); - check_tri_gadget(TriWTurn, "TriWTurn"); - check_tri_gadget(TriBranch, "TriBranch"); - check_tri_gadget(TriBranchFix, "TriBranchFix"); - check_tri_gadget(TriBranchFixB, "TriBranchFixB"); - check_tri_gadget(TriTConUp, "TriTConUp"); - check_tri_gadget(TriTConDown, "TriTConDown"); - check_tri_gadget(TriTrivialTurnLeft, "TriTrivialTurnLeft"); - check_tri_gadget(TriTrivialTurnRight, "TriTrivialTurnRight"); - check_tri_gadget(TriEndTurn, "TriEndTurn"); + check_tri_gadget(WeightedTriCross::, "TriCross"); + check_tri_gadget(WeightedTriCross::, "TriCross"); + check_tri_gadget(WeightedTriTurn, "WeightedTriTurn"); + check_tri_gadget(WeightedTriWTurn, "WeightedTriWTurn"); + check_tri_gadget(WeightedTriBranch, "WeightedTriBranch"); + check_tri_gadget(WeightedTriBranchFix, "WeightedTriBranchFix"); + check_tri_gadget(WeightedTriBranchFixB, "WeightedTriBranchFixB"); + check_tri_gadget(WeightedTriTConUp, "WeightedTriTConUp"); + check_tri_gadget(WeightedTriTConDown, "WeightedTriTConDown"); + check_tri_gadget(WeightedTriTrivialTurnLeft, "WeightedTriTrivialTurnLeft"); + check_tri_gadget(WeightedTriTrivialTurnRight, "WeightedTriTrivialTurnRight"); + check_tri_gadget(WeightedTriEndTurn, "WeightedTriEndTurn"); } // === Weighted MIS Equivalence Tests === #[test] fn test_triturn_mis_equivalence() { - // TriTurn is already weighted (WeightedTriTurn) - let gadget = TriTurn; + // WeightedTriTurn is already weighted (WeightedTriTurn) + let gadget = WeightedTriTurn; let (src_locs, src_edges, src_pins) = gadget.source_graph(); let (map_locs, map_pins) = gadget.mapped_graph(); @@ -224,15 +229,15 @@ fn test_triturn_mis_equivalence() { assert_eq!( actual, expected, - "TriTurn: expected overhead {}, got {} (src={}, map={})", + "WeightedTriTurn: expected overhead {}, got {} (src={}, map={})", expected, actual, src_mis, map_mis ); } #[test] fn test_tribranch_mis_equivalence() { - // TriBranch is already weighted (WeightedTriBranch) - let gadget = TriBranch; + // WeightedTriBranch is already weighted (WeightedTriBranch) + let gadget = WeightedTriBranch; let (src_locs, src_edges, src_pins) = gadget.source_graph(); let (map_locs, map_pins) = gadget.mapped_graph(); @@ -255,7 +260,7 @@ fn test_tribranch_mis_equivalence() { assert_eq!( actual, expected, - "TriBranch: expected overhead {}, got {} (src={}, map={})", + "WeightedTriBranch: expected overhead {}, got {} (src={}, map={})", expected, actual, src_mis, map_mis ); } @@ -263,7 +268,7 @@ fn test_tribranch_mis_equivalence() { #[test] fn test_tricross_connected_weighted_mis_equivalence() { // TriCross is already weighted (WeightedTriCross) - let gadget = TriCross::; + let gadget = WeightedTriCross::; let (source_locs, source_edges, source_pins) = gadget.source_graph(); let (mapped_locs, mapped_pins) = gadget.mapped_graph(); @@ -294,7 +299,7 @@ fn test_tricross_connected_weighted_mis_equivalence() { #[test] fn test_tricross_disconnected_weighted_mis_equivalence() { // TriCross is already weighted (WeightedTriCross) - let gadget = TriCross::; + let gadget = WeightedTriCross::; let (source_locs, source_edges, source_pins) = gadget.source_graph(); let (mapped_locs, mapped_pins) = gadget.mapped_graph(); @@ -326,7 +331,7 @@ fn test_tricross_disconnected_weighted_mis_equivalence() { fn test_all_triangular_weighted_gadgets_mis_equivalence() { // Triangular gadgets are already weighted (WeightedTri* prefix) // So we directly use their source_weights() and mapped_weights() methods - fn test_gadget(gadget: G, name: &str) { + fn test_gadget(gadget: G, name: &str) { let (src_locs, src_edges, src_pins) = gadget.source_graph(); let (map_locs, map_pins) = gadget.mapped_graph(); @@ -354,18 +359,18 @@ fn test_all_triangular_weighted_gadgets_mis_equivalence() { ); } - test_gadget(TriTurn, "TriTurn"); - test_gadget(TriBranch, "TriBranch"); - test_gadget(TriCross::, "TriCross"); - test_gadget(TriCross::, "TriCross"); - test_gadget(TriTConDown, "TriTConDown"); - test_gadget(TriTConUp, "TriTConUp"); - test_gadget(TriTrivialTurnLeft, "TriTrivialTurnLeft"); - test_gadget(TriTrivialTurnRight, "TriTrivialTurnRight"); - test_gadget(TriEndTurn, "TriEndTurn"); - test_gadget(TriWTurn, "TriWTurn"); - test_gadget(TriBranchFix, "TriBranchFix"); - test_gadget(TriBranchFixB, "TriBranchFixB"); + test_gadget(WeightedTriTurn, "WeightedTriTurn"); + test_gadget(WeightedTriBranch, "WeightedTriBranch"); + test_gadget(WeightedTriCross::, "TriCross"); + test_gadget(WeightedTriCross::, "TriCross"); + test_gadget(WeightedTriTConDown, "WeightedTriTConDown"); + test_gadget(WeightedTriTConUp, "WeightedTriTConUp"); + test_gadget(WeightedTriTrivialTurnLeft, "WeightedTriTrivialTurnLeft"); + test_gadget(WeightedTriTrivialTurnRight, "WeightedTriTrivialTurnRight"); + test_gadget(WeightedTriEndTurn, "WeightedTriEndTurn"); + test_gadget(WeightedTriWTurn, "WeightedTriWTurn"); + test_gadget(WeightedTriBranchFix, "WeightedTriBranchFix"); + test_gadget(WeightedTriBranchFixB, "WeightedTriBranchFixB"); } // === KSG Weighted Gadget Tests === @@ -831,13 +836,13 @@ fn test_all_ksg_weighted_gadgets_mis_equivalence() { #[test] fn test_pattern_source_matrix() { // Test source_matrix generation for all gadgets - let cross_matrix = Cross::.source_matrix(); + let cross_matrix = KsgCross::.source_matrix(); assert!(!cross_matrix.is_empty()); - let turn_matrix = Turn.source_matrix(); + let turn_matrix = KsgTurn.source_matrix(); assert!(!turn_matrix.is_empty()); - let branch_matrix = Branch.source_matrix(); + let branch_matrix = KsgBranch.source_matrix(); assert!(!branch_matrix.is_empty()); } @@ -857,10 +862,10 @@ fn test_weighted_ksg_pattern_source_matrix() { fn test_pattern_mapped_matrix() { use crate::rules::unitdiskmapping::Pattern; - let cross_mapped = Cross::.mapped_matrix(); + let cross_mapped = KsgCross::.mapped_matrix(); assert!(!cross_mapped.is_empty()); - let turn_mapped = Turn.mapped_matrix(); + let turn_mapped = KsgTurn.mapped_matrix(); assert!(!turn_mapped.is_empty()); } @@ -910,8 +915,8 @@ fn test_all_weighted_gadgets_weights_positive() { #[test] fn test_gadget_is_connected_variants() { // Test is_connected() method - assert!(Cross::.is_connected()); - assert!(!Cross::.is_connected()); + assert!(KsgCross::.is_connected()); + assert!(!KsgCross::.is_connected()); assert!(WeightedKsgCross::.is_connected()); assert!(!WeightedKsgCross::.is_connected()); @@ -920,20 +925,20 @@ fn test_gadget_is_connected_variants() { #[test] fn test_gadget_is_cross_gadget() { // Cross gadgets should return true - assert!(Cross::.is_cross_gadget()); - assert!(Cross::.is_cross_gadget()); + assert!(KsgCross::.is_cross_gadget()); + assert!(KsgCross::.is_cross_gadget()); assert!(WeightedKsgCross::.is_cross_gadget()); assert!(WeightedKsgCross::.is_cross_gadget()); // Non-cross gadgets should return false - assert!(!Turn.is_cross_gadget()); + assert!(!KsgTurn.is_cross_gadget()); assert!(!WeightedKsgTurn.is_cross_gadget()); } #[test] fn test_gadget_connected_nodes() { // Connected gadgets should have connected_nodes - let nodes = Cross::.connected_nodes(); + let nodes = KsgCross::.connected_nodes(); assert!(!nodes.is_empty()); let weighted_nodes = WeightedKsgCross::.connected_nodes(); @@ -978,32 +983,32 @@ fn test_build_triangular_unit_disk_edges() { #[test] fn test_triangular_gadget_source_matrix() { - let matrix = TriTurn.source_matrix(); + let matrix = WeightedTriTurn.source_matrix(); assert!(!matrix.is_empty()); - let matrix = TriCross::.source_matrix(); + let matrix = WeightedTriCross::.source_matrix(); assert!(!matrix.is_empty()); - let matrix = TriBranch.source_matrix(); + let matrix = WeightedTriBranch.source_matrix(); assert!(!matrix.is_empty()); } #[test] fn test_triangular_gadget_mapped_matrix() { - use crate::rules::unitdiskmapping::TriangularGadget; + use crate::rules::unitdiskmapping::triangular::WeightedTriangularGadget; - let matrix = TriTurn.mapped_matrix(); + let matrix = WeightedTriTurn.mapped_matrix(); assert!(!matrix.is_empty()); - let matrix = TriCross::.mapped_matrix(); + let matrix = WeightedTriCross::.mapped_matrix(); assert!(!matrix.is_empty()); } #[test] fn test_triangular_gadget_weights() { // Test that weights are returned correctly - let src_weights = TriTurn.source_weights(); - let map_weights = TriTurn.mapped_weights(); + let src_weights = WeightedTriTurn.source_weights(); + let map_weights = WeightedTriTurn.mapped_weights(); assert!(!src_weights.is_empty()); assert!(!map_weights.is_empty()); @@ -1015,19 +1020,19 @@ fn test_triangular_gadget_weights() { #[test] fn test_triangular_gadget_connected_nodes() { // Test connected gadgets - let nodes = TriCross::.connected_nodes(); + let nodes = WeightedTriCross::.connected_nodes(); // TriCross should have connected nodes - assert!(!nodes.is_empty() || TriCross::.is_connected()); + assert!(!nodes.is_empty() || WeightedTriCross::.is_connected()); // TriCross should not be connected - assert!(!TriCross::.is_connected()); + assert!(!WeightedTriCross::.is_connected()); } #[test] fn test_all_triangular_gadgets_source_matrix() { - use crate::rules::unitdiskmapping::TriangularGadget; + use crate::rules::unitdiskmapping::triangular::WeightedTriangularGadget; - fn check_matrix(gadget: G, name: &str) { + fn check_matrix(gadget: G, name: &str) { let matrix = gadget.source_matrix(); let (rows, cols) = gadget.size(); assert_eq!( @@ -1046,25 +1051,25 @@ fn test_all_triangular_gadgets_source_matrix() { } } - check_matrix(TriTurn, "TriTurn"); - check_matrix(TriCross::, "TriCross"); - check_matrix(TriCross::, "TriCross"); - check_matrix(TriBranch, "TriBranch"); - check_matrix(TriBranchFix, "TriBranchFix"); - check_matrix(TriBranchFixB, "TriBranchFixB"); - check_matrix(TriTConUp, "TriTConUp"); - check_matrix(TriTConDown, "TriTConDown"); - check_matrix(TriTrivialTurnLeft, "TriTrivialTurnLeft"); - check_matrix(TriTrivialTurnRight, "TriTrivialTurnRight"); - check_matrix(TriEndTurn, "TriEndTurn"); - check_matrix(TriWTurn, "TriWTurn"); + check_matrix(WeightedTriTurn, "WeightedTriTurn"); + check_matrix(WeightedTriCross::, "TriCross"); + check_matrix(WeightedTriCross::, "TriCross"); + check_matrix(WeightedTriBranch, "WeightedTriBranch"); + check_matrix(WeightedTriBranchFix, "WeightedTriBranchFix"); + check_matrix(WeightedTriBranchFixB, "WeightedTriBranchFixB"); + check_matrix(WeightedTriTConUp, "WeightedTriTConUp"); + check_matrix(WeightedTriTConDown, "WeightedTriTConDown"); + check_matrix(WeightedTriTrivialTurnLeft, "WeightedTriTrivialTurnLeft"); + check_matrix(WeightedTriTrivialTurnRight, "WeightedTriTrivialTurnRight"); + check_matrix(WeightedTriEndTurn, "WeightedTriEndTurn"); + check_matrix(WeightedTriWTurn, "WeightedTriWTurn"); } #[test] fn test_all_triangular_gadgets_mapped_matrix() { - use crate::rules::unitdiskmapping::TriangularGadget; + use crate::rules::unitdiskmapping::triangular::WeightedTriangularGadget; - fn check_matrix(gadget: G, name: &str) { + fn check_matrix(gadget: G, name: &str) { let matrix = gadget.mapped_matrix(); let (rows, cols) = gadget.size(); assert_eq!( @@ -1083,44 +1088,44 @@ fn test_all_triangular_gadgets_mapped_matrix() { } } - check_matrix(TriTurn, "TriTurn"); - check_matrix(TriCross::, "TriCross"); - check_matrix(TriCross::, "TriCross"); - check_matrix(TriBranch, "TriBranch"); - check_matrix(TriBranchFix, "TriBranchFix"); - check_matrix(TriBranchFixB, "TriBranchFixB"); - check_matrix(TriTConUp, "TriTConUp"); - check_matrix(TriTConDown, "TriTConDown"); - check_matrix(TriTrivialTurnLeft, "TriTrivialTurnLeft"); - check_matrix(TriTrivialTurnRight, "TriTrivialTurnRight"); - check_matrix(TriEndTurn, "TriEndTurn"); - check_matrix(TriWTurn, "TriWTurn"); + check_matrix(WeightedTriTurn, "WeightedTriTurn"); + check_matrix(WeightedTriCross::, "TriCross"); + check_matrix(WeightedTriCross::, "TriCross"); + check_matrix(WeightedTriBranch, "WeightedTriBranch"); + check_matrix(WeightedTriBranchFix, "WeightedTriBranchFix"); + check_matrix(WeightedTriBranchFixB, "WeightedTriBranchFixB"); + check_matrix(WeightedTriTConUp, "WeightedTriTConUp"); + check_matrix(WeightedTriTConDown, "WeightedTriTConDown"); + check_matrix(WeightedTriTrivialTurnLeft, "WeightedTriTrivialTurnLeft"); + check_matrix(WeightedTriTrivialTurnRight, "WeightedTriTrivialTurnRight"); + check_matrix(WeightedTriEndTurn, "WeightedTriEndTurn"); + check_matrix(WeightedTriWTurn, "WeightedTriWTurn"); } // === Rotated/Reflected Gadget Wrapper Tests === #[test] fn test_rotated_gadget_size() { - let base = Turn; + let base = KsgTurn; let (m, n) = base.size(); // 90 degree rotation swaps dimensions - let rot90 = RotatedGadget::new(base, 1); + let rot90 = KsgRotatedGadget::new(base, 1); assert_eq!(rot90.size(), (n, m)); // 180 degree keeps dimensions - let rot180 = RotatedGadget::new(base, 2); + let rot180 = KsgRotatedGadget::new(base, 2); assert_eq!(rot180.size(), (m, n)); // 270 degree swaps dimensions - let rot270 = RotatedGadget::new(base, 3); + let rot270 = KsgRotatedGadget::new(base, 3); assert_eq!(rot270.size(), (n, m)); } #[test] fn test_rotated_gadget_cross_location() { - let base = Cross::; - let rotated = RotatedGadget::new(base, 1); + let base = KsgCross::; + let rotated = KsgRotatedGadget::new(base, 1); // Cross location should be valid for rotated gadget let (r, c) = rotated.cross_location(); @@ -1131,8 +1136,8 @@ fn test_rotated_gadget_cross_location() { #[test] fn test_rotated_gadget_source_graph() { - let base = Turn; - let rotated = RotatedGadget::new(base, 1); + let base = KsgTurn; + let rotated = KsgRotatedGadget::new(base, 1); let (locs, edges, pins) = rotated.source_graph(); let (rows, cols) = rotated.size(); @@ -1156,8 +1161,8 @@ fn test_rotated_gadget_source_graph() { #[test] fn test_rotated_gadget_mapped_graph() { - let base = Branch; - let rotated = RotatedGadget::new(base, 2); + let base = KsgBranch; + let rotated = KsgRotatedGadget::new(base, 2); let (locs, pins) = rotated.mapped_graph(); let (rows, cols) = rotated.size(); @@ -1176,8 +1181,8 @@ fn test_rotated_gadget_mapped_graph() { #[test] fn test_rotated_gadget_preserves_mis_overhead() { - let base = Turn; - let rotated = RotatedGadget::new(base, 1); + let base = KsgTurn; + let rotated = KsgRotatedGadget::new(base, 1); // MIS overhead should be same for rotated gadget assert_eq!(base.mis_overhead(), rotated.mis_overhead()); @@ -1186,7 +1191,7 @@ fn test_rotated_gadget_preserves_mis_overhead() { #[test] fn test_rotated_gadget_preserves_weights() { let base = WeightedKsgTurn; - let rotated = RotatedGadget::new(base, 2); + let rotated = KsgRotatedGadget::new(base, 2); // Weights don't change with rotation assert_eq!(base.source_weights(), rotated.source_weights()); @@ -1195,8 +1200,8 @@ fn test_rotated_gadget_preserves_weights() { #[test] fn test_rotated_gadget_delegates_properties() { - let base = Cross::; - let rotated = RotatedGadget::new(base, 1); + let base = KsgCross::; + let rotated = KsgRotatedGadget::new(base, 1); assert_eq!(base.is_connected(), rotated.is_connected()); assert_eq!(base.is_cross_gadget(), rotated.is_cross_gadget()); @@ -1205,36 +1210,36 @@ fn test_rotated_gadget_delegates_properties() { #[test] fn test_reflected_gadget_size_x_y() { - let base = Turn; + let base = KsgTurn; let (m, n) = base.size(); // X and Y mirror keep same dimensions - let ref_x = ReflectedGadget::new(base, Mirror::X); + let ref_x = KsgReflectedGadget::new(base, Mirror::X); assert_eq!(ref_x.size(), (m, n)); - let ref_y = ReflectedGadget::new(base, Mirror::Y); + let ref_y = KsgReflectedGadget::new(base, Mirror::Y); assert_eq!(ref_y.size(), (m, n)); } #[test] fn test_reflected_gadget_size_diagonal() { - let base = Turn; + let base = KsgTurn; let (m, n) = base.size(); // Diagonal mirrors swap dimensions - let ref_diag = ReflectedGadget::new(base, Mirror::Diag); + let ref_diag = KsgReflectedGadget::new(base, Mirror::Diag); assert_eq!(ref_diag.size(), (n, m)); - let ref_offdiag = ReflectedGadget::new(base, Mirror::OffDiag); + let ref_offdiag = KsgReflectedGadget::new(base, Mirror::OffDiag); assert_eq!(ref_offdiag.size(), (n, m)); } #[test] fn test_reflected_gadget_cross_location() { - let base = Cross::; + let base = KsgCross::; for mirror in [Mirror::X, Mirror::Y, Mirror::Diag, Mirror::OffDiag] { - let reflected = ReflectedGadget::new(base, mirror); + let reflected = KsgReflectedGadget::new(base, mirror); let (r, c) = reflected.cross_location(); let (rows, cols) = reflected.size(); assert!(r > 0 && r <= rows, "mirror {:?}: row out of bounds", mirror); @@ -1244,8 +1249,8 @@ fn test_reflected_gadget_cross_location() { #[test] fn test_reflected_gadget_source_graph() { - let base = Branch; - let reflected = ReflectedGadget::new(base, Mirror::X); + let base = KsgBranch; + let reflected = KsgReflectedGadget::new(base, Mirror::X); let (locs, edges, pins) = reflected.source_graph(); let (rows, cols) = reflected.size(); @@ -1269,8 +1274,8 @@ fn test_reflected_gadget_source_graph() { #[test] fn test_reflected_gadget_mapped_graph() { - let base = TCon; - let reflected = ReflectedGadget::new(base, Mirror::Y); + let base = KsgTCon; + let reflected = KsgReflectedGadget::new(base, Mirror::Y); let (locs, pins) = reflected.mapped_graph(); let (rows, cols) = reflected.size(); @@ -1287,8 +1292,8 @@ fn test_reflected_gadget_mapped_graph() { #[test] fn test_reflected_gadget_preserves_mis_overhead() { - let base = Turn; - let reflected = ReflectedGadget::new(base, Mirror::Diag); + let base = KsgTurn; + let reflected = KsgReflectedGadget::new(base, Mirror::Diag); assert_eq!(base.mis_overhead(), reflected.mis_overhead()); } @@ -1296,7 +1301,7 @@ fn test_reflected_gadget_preserves_mis_overhead() { #[test] fn test_reflected_gadget_preserves_weights() { let base = WeightedKsgBranch; - let reflected = ReflectedGadget::new(base, Mirror::OffDiag); + let reflected = KsgReflectedGadget::new(base, Mirror::OffDiag); assert_eq!(base.source_weights(), reflected.source_weights()); assert_eq!(base.mapped_weights(), reflected.mapped_weights()); @@ -1304,8 +1309,8 @@ fn test_reflected_gadget_preserves_weights() { #[test] fn test_reflected_gadget_delegates_properties() { - let base = Cross::; - let reflected = ReflectedGadget::new(base, Mirror::X); + let base = KsgCross::; + let reflected = KsgReflectedGadget::new(base, Mirror::X); assert_eq!(base.is_connected(), reflected.is_connected()); assert_eq!(base.is_cross_gadget(), reflected.is_cross_gadget()); @@ -1316,7 +1321,7 @@ fn test_reflected_gadget_delegates_properties() { fn test_all_rotations_valid_graphs() { fn check_rotated(gadget: G, name: &str) { for n in 0..4 { - let rotated = RotatedGadget::new(gadget, n); + let rotated = KsgRotatedGadget::new(gadget, n); let (src_locs, src_edges, src_pins) = rotated.source_graph(); let (map_locs, map_pins) = rotated.mapped_graph(); @@ -1345,17 +1350,17 @@ fn test_all_rotations_valid_graphs() { } } - check_rotated(Turn, "Turn"); - check_rotated(Branch, "Branch"); - check_rotated(Cross::, "Cross"); - check_rotated(TCon, "TCon"); + check_rotated(KsgTurn, "KsgTurn"); + check_rotated(KsgBranch, "KsgBranch"); + check_rotated(KsgCross::, "KsgCross"); + check_rotated(KsgTCon, "KsgTCon"); } #[test] fn test_all_mirrors_valid_graphs() { fn check_mirrored(gadget: G, name: &str) { for mirror in [Mirror::X, Mirror::Y, Mirror::Diag, Mirror::OffDiag] { - let reflected = ReflectedGadget::new(gadget, mirror); + let reflected = KsgReflectedGadget::new(gadget, mirror); let (src_locs, src_edges, src_pins) = reflected.source_graph(); let (map_locs, map_pins) = reflected.mapped_graph(); @@ -1384,17 +1389,15 @@ fn test_all_mirrors_valid_graphs() { } } - check_mirrored(Turn, "Turn"); - check_mirrored(Branch, "Branch"); - check_mirrored(Cross::, "Cross"); - check_mirrored(TCon, "TCon"); + check_mirrored(KsgTurn, "KsgTurn"); + check_mirrored(KsgBranch, "KsgBranch"); + check_mirrored(KsgCross::, "KsgCross"); + check_mirrored(KsgTCon, "KsgTCon"); } // === Julia Tests: rotated_and_reflected counts === // From Julia's test/gadgets.jl -use crate::rules::unitdiskmapping::{BranchFixB, DanglingLeg}; - /// Count unique gadgets from all rotations (0, 1, 2, 3) and reflections (X, Y, Diag, OffDiag). /// Julia: length(rotated_and_reflected(gadget)) fn count_rotated_and_reflected(gadget: G) -> usize { @@ -1404,14 +1407,14 @@ fn count_rotated_and_reflected(gadget: G) - // All rotations (0, 90, 180, 270 degrees) for n in 0..4 { - let rotated = RotatedGadget::new(gadget, n); + let rotated = KsgRotatedGadget::new(gadget, n); let (locs, _, _) = rotated.source_graph(); unique.insert(format!("{:?}", locs)); } // All reflections for mirror in [Mirror::X, Mirror::Y, Mirror::Diag, Mirror::OffDiag] { - let reflected = ReflectedGadget::new(gadget, mirror); + let reflected = KsgReflectedGadget::new(gadget, mirror); let (locs, _, _) = reflected.source_graph(); unique.insert(format!("{:?}", locs)); } @@ -1422,7 +1425,7 @@ fn count_rotated_and_reflected(gadget: G) - #[test] fn test_rotated_and_reflected_danglingleg() { // Julia: @test length(rotated_and_reflected(UnitDiskMapping.DanglingLeg())) == 4 - let count = count_rotated_and_reflected(DanglingLeg); + let count = count_rotated_and_reflected(KsgDanglingLeg); assert_eq!(count, 4, "DanglingLeg should have 4 unique orientations"); } @@ -1431,7 +1434,7 @@ fn test_rotated_and_reflected_cross_false() { // Julia: @test length(rotated_and_reflected(Cross{false}())) == 4 // Cross has 4-fold rotational symmetry, so rotations produce duplicates // But reflections may produce different locations in our representation - let count = count_rotated_and_reflected(Cross::); + let count = count_rotated_and_reflected(KsgCross::); // Cross should have limited unique orientations due to symmetry assert!( count > 0, @@ -1446,7 +1449,7 @@ fn test_rotated_and_reflected_cross_false() { #[test] fn test_rotated_and_reflected_cross_true() { // Julia: @test length(rotated_and_reflected(Cross{true}())) == 4 - let count = count_rotated_and_reflected(Cross::); + let count = count_rotated_and_reflected(KsgCross::); assert!( count > 0, "Cross should have some unique orientations" @@ -1460,7 +1463,7 @@ fn test_rotated_and_reflected_cross_true() { #[test] fn test_rotated_and_reflected_branchfixb() { // Julia: @test length(rotated_and_reflected(BranchFixB())) == 8 - let count = count_rotated_and_reflected(BranchFixB); + let count = count_rotated_and_reflected(KsgBranchFixB); assert_eq!(count, 8, "BranchFixB should have 8 unique orientations"); } @@ -1470,14 +1473,14 @@ fn test_rotated_and_reflected_branchfixb() { #[test] fn test_danglingleg_size() { // Julia: @test size(p) == (4, 3) - let gadget = DanglingLeg; + let gadget = KsgDanglingLeg; assert_eq!(gadget.size(), (4, 3), "DanglingLeg size should be (4, 3)"); } #[test] fn test_danglingleg_source_locations() { // Julia: @test UnitDiskMapping.source_locations(p) == UnitDiskMapping.Node.([(2,2), (3,2), (4,2)]) - let gadget = DanglingLeg; + let gadget = KsgDanglingLeg; let (locs, _, _) = gadget.source_graph(); // Julia is 1-indexed, Rust is 1-indexed for gadget coordinates @@ -1488,7 +1491,7 @@ fn test_danglingleg_source_locations() { #[test] fn test_danglingleg_mapped_locations() { // Julia: @test UnitDiskMapping.mapped_locations(p) == UnitDiskMapping.Node.([(4,2)]) - let gadget = DanglingLeg; + let gadget = KsgDanglingLeg; let (locs, _) = gadget.mapped_graph(); // Julia is 1-indexed @@ -1498,7 +1501,7 @@ fn test_danglingleg_mapped_locations() { #[test] fn test_danglingleg_mis_overhead() { - let gadget = DanglingLeg; + let gadget = KsgDanglingLeg; // DanglingLeg simplifies 3 nodes to 1, removing 2 from MIS assert_eq!( gadget.mis_overhead(), diff --git a/src/unit_tests/unitdiskmapping_algorithms/gadgets_ground_truth.rs b/src/unit_tests/unitdiskmapping_algorithms/gadgets_ground_truth.rs index 1c0e7d7e..72656041 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/gadgets_ground_truth.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/gadgets_ground_truth.rs @@ -3,37 +3,17 @@ //! The ground truth is generated by scripts/dump_gadgets.jl and stored in //! tests/data/gadgets_ground_truth.json -use crate::rules::unitdiskmapping::{ - // Unweighted square gadgets - Branch, - BranchFix, - BranchFixB, - Cross, - DanglingLeg, - EndTurn, - Mirror, - Pattern, - ReflectedGadget, - RotatedGadget, - TCon, - // Triangular gadgets - TriBranch, - TriBranchFix, - TriBranchFixB, - TriCross, - TriEndTurn, - TriTConDown, - TriTConLeft, - TriTConUp, - TriTrivialTurnLeft, - TriTrivialTurnRight, - TriTurn, - TriWTurn, - TriangularGadget, - TrivialTurn, - Turn, - WTurn, +use crate::rules::unitdiskmapping::ksg::{ + KsgBranch, KsgBranchFix, KsgBranchFixB, KsgCross, KsgDanglingLeg, KsgEndTurn, + KsgReflectedGadget, KsgRotatedGadget, KsgTCon, KsgTrivialTurn, KsgTurn, KsgWTurn, Mirror, }; +use crate::rules::unitdiskmapping::triangular::{ + WeightedTriBranch, WeightedTriBranchFix, WeightedTriBranchFixB, WeightedTriCross, + WeightedTriEndTurn, WeightedTriTConDown, WeightedTriTConLeft, WeightedTriTConUp, + WeightedTriTrivialTurnLeft, WeightedTriTrivialTurnRight, WeightedTriTurn, WeightedTriWTurn, + WeightedTriangularGadget, +}; +use crate::rules::unitdiskmapping::Pattern; use serde::Deserialize; use std::collections::HashMap; use std::fs; @@ -229,77 +209,77 @@ macro_rules! check_weighted_gadget { fn test_unweighted_square_cross_false() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("Cross_false", Cross::, map["Cross_false"]); + check_gadget!("Cross_false", KsgCross::, map["Cross_false"]); } #[test] fn test_unweighted_square_cross_true() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("Cross_true", Cross::, map["Cross_true"]); + check_gadget!("Cross_true", KsgCross::, map["Cross_true"]); } #[test] fn test_unweighted_square_turn() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("Turn", Turn, map["Turn"]); + check_gadget!("Turn", KsgTurn, map["Turn"]); } #[test] fn test_unweighted_square_wturn() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("WTurn", WTurn, map["WTurn"]); + check_gadget!("WTurn", KsgWTurn, map["WTurn"]); } #[test] fn test_unweighted_square_branch() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("Branch", Branch, map["Branch"]); + check_gadget!("Branch", KsgBranch, map["Branch"]); } #[test] fn test_unweighted_square_branchfix() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("BranchFix", BranchFix, map["BranchFix"]); + check_gadget!("BranchFix", KsgBranchFix, map["BranchFix"]); } #[test] fn test_unweighted_square_branchfixb() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("BranchFixB", BranchFixB, map["BranchFixB"]); + check_gadget!("BranchFixB", KsgBranchFixB, map["BranchFixB"]); } #[test] fn test_unweighted_square_tcon() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("TCon", TCon, map["TCon"]); + check_gadget!("TCon", KsgTCon, map["TCon"]); } #[test] fn test_unweighted_square_trivialturn() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("TrivialTurn", TrivialTurn, map["TrivialTurn"]); + check_gadget!("TrivialTurn", KsgTrivialTurn, map["TrivialTurn"]); } #[test] fn test_unweighted_square_endturn() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("EndTurn", EndTurn, map["EndTurn"]); + check_gadget!("EndTurn", KsgEndTurn, map["EndTurn"]); } #[test] fn test_unweighted_square_danglingleg() { let gt = load_ground_truth(); let map = to_map(>.unweighted_square); - check_gadget!("DanglingLeg", DanglingLeg, map["DanglingLeg"]); + check_gadget!("DanglingLeg", KsgDanglingLeg, map["DanglingLeg"]); } // === Triangular Gadget Tests === @@ -308,35 +288,35 @@ fn test_unweighted_square_danglingleg() { fn test_triangular_cross_false() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriCross_false", TriCross::, map["TriCross_false"]); + check_gadget!("TriCross_false", WeightedTriCross::, map["TriCross_false"]); } #[test] fn test_triangular_cross_true() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriCross_true", TriCross::, map["TriCross_true"]); + check_gadget!("TriCross_true", WeightedTriCross::, map["TriCross_true"]); } #[test] fn test_triangular_tcon_left() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriTCon_left", TriTConLeft, map["TriTCon_left"]); + check_gadget!("TriTCon_left", WeightedTriTConLeft, map["TriTCon_left"]); } #[test] fn test_triangular_tcon_up() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriTCon_up", TriTConUp, map["TriTCon_up"]); + check_gadget!("TriTCon_up", WeightedTriTConUp, map["TriTCon_up"]); } #[test] fn test_triangular_tcon_down() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriTCon_down", TriTConDown, map["TriTCon_down"]); + check_gadget!("TriTCon_down", WeightedTriTConDown, map["TriTCon_down"]); } #[test] @@ -345,7 +325,7 @@ fn test_triangular_trivialturn_left() { let map = to_map(>.triangular); check_gadget!( "TriTrivialTurn_left", - TriTrivialTurnLeft, + WeightedTriTrivialTurnLeft, map["TriTrivialTurn_left"] ); } @@ -356,7 +336,7 @@ fn test_triangular_trivialturn_right() { let map = to_map(>.triangular); check_gadget!( "TriTrivialTurn_right", - TriTrivialTurnRight, + WeightedTriTrivialTurnRight, map["TriTrivialTurn_right"] ); } @@ -365,42 +345,42 @@ fn test_triangular_trivialturn_right() { fn test_triangular_endturn() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriEndTurn", TriEndTurn, map["TriEndTurn"]); + check_gadget!("TriEndTurn", WeightedTriEndTurn, map["TriEndTurn"]); } #[test] fn test_triangular_turn() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriTurn", TriTurn, map["TriTurn"]); + check_gadget!("TriTurn", WeightedTriTurn, map["TriTurn"]); } #[test] fn test_triangular_wturn() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriWTurn", TriWTurn, map["TriWTurn"]); + check_gadget!("TriWTurn", WeightedTriWTurn, map["TriWTurn"]); } #[test] fn test_triangular_branchfix() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriBranchFix", TriBranchFix, map["TriBranchFix"]); + check_gadget!("TriBranchFix", WeightedTriBranchFix, map["TriBranchFix"]); } #[test] fn test_triangular_branchfixb() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriBranchFixB", TriBranchFixB, map["TriBranchFixB"]); + check_gadget!("TriBranchFixB", WeightedTriBranchFixB, map["TriBranchFixB"]); } #[test] fn test_triangular_branch() { let gt = load_ground_truth(); let map = to_map(>.triangular); - check_gadget!("TriBranch", TriBranch, map["TriBranch"]); + check_gadget!("TriBranch", WeightedTriBranch, map["TriBranch"]); } // === Weighted Square Gadget Tests === @@ -409,77 +389,77 @@ fn test_triangular_branch() { fn test_weighted_square_cross_false() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("Cross_false", Cross::, map["Cross_false"]); + check_weighted_gadget!("Cross_false", KsgCross::, map["Cross_false"]); } #[test] fn test_weighted_square_cross_true() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("Cross_true", Cross::, map["Cross_true"]); + check_weighted_gadget!("Cross_true", KsgCross::, map["Cross_true"]); } #[test] fn test_weighted_square_turn() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("Turn", Turn, map["Turn"]); + check_weighted_gadget!("Turn", KsgTurn, map["Turn"]); } #[test] fn test_weighted_square_wturn() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("WTurn", WTurn, map["WTurn"]); + check_weighted_gadget!("WTurn", KsgWTurn, map["WTurn"]); } #[test] fn test_weighted_square_branch() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("Branch", Branch, map["Branch"]); + check_weighted_gadget!("Branch", KsgBranch, map["Branch"]); } #[test] fn test_weighted_square_branchfix() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("BranchFix", BranchFix, map["BranchFix"]); + check_weighted_gadget!("BranchFix", KsgBranchFix, map["BranchFix"]); } #[test] fn test_weighted_square_branchfixb() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("BranchFixB", BranchFixB, map["BranchFixB"]); + check_weighted_gadget!("BranchFixB", KsgBranchFixB, map["BranchFixB"]); } #[test] fn test_weighted_square_tcon() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("TCon", TCon, map["TCon"]); + check_weighted_gadget!("TCon", KsgTCon, map["TCon"]); } #[test] fn test_weighted_square_trivialturn() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("TrivialTurn", TrivialTurn, map["TrivialTurn"]); + check_weighted_gadget!("TrivialTurn", KsgTrivialTurn, map["TrivialTurn"]); } #[test] fn test_weighted_square_endturn() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("EndTurn", EndTurn, map["EndTurn"]); + check_weighted_gadget!("EndTurn", KsgEndTurn, map["EndTurn"]); } #[test] fn test_weighted_square_danglingleg() { let gt = load_ground_truth(); let map = to_map(>.weighted_square); - check_weighted_gadget!("DanglingLeg", DanglingLeg, map["DanglingLeg"]); + check_weighted_gadget!("DanglingLeg", KsgDanglingLeg, map["DanglingLeg"]); } // === Rotated Gadget Tests === @@ -490,7 +470,7 @@ macro_rules! test_rotated { fn $test_name() { let gt = load_ground_truth(); let map = to_map(>.rotated); - let gadget = RotatedGadget::new($base, $n); + let gadget = KsgRotatedGadget::new($base, $n); check_gadget!($key, gadget, map[$key]); } }; @@ -502,7 +482,7 @@ macro_rules! test_reflected { fn $test_name() { let gt = load_ground_truth(); let map = to_map(>.reflected); - let gadget = ReflectedGadget::new($base, $mirror); + let gadget = KsgReflectedGadget::new($base, $mirror); check_gadget!($key, gadget, map[$key]); } }; @@ -511,19 +491,19 @@ macro_rules! test_reflected { // Cross rotations test_rotated!( test_rotated_cross_false_rot1, - Cross::, + KsgCross::, 1, "Cross_false_rot1" ); test_rotated!( test_rotated_cross_false_rot2, - Cross::, + KsgCross::, 2, "Cross_false_rot2" ); test_rotated!( test_rotated_cross_false_rot3, - Cross::, + KsgCross::, 3, "Cross_false_rot3" ); @@ -531,109 +511,109 @@ test_rotated!( // Cross rotations test_rotated!( test_rotated_cross_true_rot1, - Cross::, + KsgCross::, 1, "Cross_true_rot1" ); test_rotated!( test_rotated_cross_true_rot2, - Cross::, + KsgCross::, 2, "Cross_true_rot2" ); test_rotated!( test_rotated_cross_true_rot3, - Cross::, + KsgCross::, 3, "Cross_true_rot3" ); // Turn rotations -test_rotated!(test_rotated_turn_rot1, Turn, 1, "Turn_rot1"); -test_rotated!(test_rotated_turn_rot2, Turn, 2, "Turn_rot2"); -test_rotated!(test_rotated_turn_rot3, Turn, 3, "Turn_rot3"); +test_rotated!(test_rotated_turn_rot1, KsgTurn, 1, "Turn_rot1"); +test_rotated!(test_rotated_turn_rot2, KsgTurn, 2, "Turn_rot2"); +test_rotated!(test_rotated_turn_rot3, KsgTurn, 3, "Turn_rot3"); // WTurn rotations -test_rotated!(test_rotated_wturn_rot1, WTurn, 1, "WTurn_rot1"); -test_rotated!(test_rotated_wturn_rot2, WTurn, 2, "WTurn_rot2"); -test_rotated!(test_rotated_wturn_rot3, WTurn, 3, "WTurn_rot3"); +test_rotated!(test_rotated_wturn_rot1, KsgWTurn, 1, "WTurn_rot1"); +test_rotated!(test_rotated_wturn_rot2, KsgWTurn, 2, "WTurn_rot2"); +test_rotated!(test_rotated_wturn_rot3, KsgWTurn, 3, "WTurn_rot3"); // Branch rotations -test_rotated!(test_rotated_branch_rot1, Branch, 1, "Branch_rot1"); -test_rotated!(test_rotated_branch_rot2, Branch, 2, "Branch_rot2"); -test_rotated!(test_rotated_branch_rot3, Branch, 3, "Branch_rot3"); +test_rotated!(test_rotated_branch_rot1, KsgBranch, 1, "Branch_rot1"); +test_rotated!(test_rotated_branch_rot2, KsgBranch, 2, "Branch_rot2"); +test_rotated!(test_rotated_branch_rot3, KsgBranch, 3, "Branch_rot3"); // BranchFix rotations -test_rotated!(test_rotated_branchfix_rot1, BranchFix, 1, "BranchFix_rot1"); -test_rotated!(test_rotated_branchfix_rot2, BranchFix, 2, "BranchFix_rot2"); -test_rotated!(test_rotated_branchfix_rot3, BranchFix, 3, "BranchFix_rot3"); +test_rotated!(test_rotated_branchfix_rot1, KsgBranchFix, 1, "BranchFix_rot1"); +test_rotated!(test_rotated_branchfix_rot2, KsgBranchFix, 2, "BranchFix_rot2"); +test_rotated!(test_rotated_branchfix_rot3, KsgBranchFix, 3, "BranchFix_rot3"); // BranchFixB rotations test_rotated!( test_rotated_branchfixb_rot1, - BranchFixB, + KsgBranchFixB, 1, "BranchFixB_rot1" ); test_rotated!( test_rotated_branchfixb_rot2, - BranchFixB, + KsgBranchFixB, 2, "BranchFixB_rot2" ); test_rotated!( test_rotated_branchfixb_rot3, - BranchFixB, + KsgBranchFixB, 3, "BranchFixB_rot3" ); // TCon rotations -test_rotated!(test_rotated_tcon_rot1, TCon, 1, "TCon_rot1"); -test_rotated!(test_rotated_tcon_rot2, TCon, 2, "TCon_rot2"); -test_rotated!(test_rotated_tcon_rot3, TCon, 3, "TCon_rot3"); +test_rotated!(test_rotated_tcon_rot1, KsgTCon, 1, "TCon_rot1"); +test_rotated!(test_rotated_tcon_rot2, KsgTCon, 2, "TCon_rot2"); +test_rotated!(test_rotated_tcon_rot3, KsgTCon, 3, "TCon_rot3"); // TrivialTurn rotations test_rotated!( test_rotated_trivialturn_rot1, - TrivialTurn, + KsgTrivialTurn, 1, "TrivialTurn_rot1" ); test_rotated!( test_rotated_trivialturn_rot2, - TrivialTurn, + KsgTrivialTurn, 2, "TrivialTurn_rot2" ); test_rotated!( test_rotated_trivialturn_rot3, - TrivialTurn, + KsgTrivialTurn, 3, "TrivialTurn_rot3" ); // EndTurn rotations -test_rotated!(test_rotated_endturn_rot1, EndTurn, 1, "EndTurn_rot1"); -test_rotated!(test_rotated_endturn_rot2, EndTurn, 2, "EndTurn_rot2"); -test_rotated!(test_rotated_endturn_rot3, EndTurn, 3, "EndTurn_rot3"); +test_rotated!(test_rotated_endturn_rot1, KsgEndTurn, 1, "EndTurn_rot1"); +test_rotated!(test_rotated_endturn_rot2, KsgEndTurn, 2, "EndTurn_rot2"); +test_rotated!(test_rotated_endturn_rot3, KsgEndTurn, 3, "EndTurn_rot3"); // DanglingLeg rotations test_rotated!( test_rotated_danglingleg_rot1, - DanglingLeg, + KsgDanglingLeg, 1, "DanglingLeg_rot1" ); test_rotated!( test_rotated_danglingleg_rot2, - DanglingLeg, + KsgDanglingLeg, 2, "DanglingLeg_rot2" ); test_rotated!( test_rotated_danglingleg_rot3, - DanglingLeg, + KsgDanglingLeg, 3, "DanglingLeg_rot3" ); @@ -643,25 +623,25 @@ test_rotated!( // Cross reflections test_reflected!( test_reflected_cross_false_x, - Cross::, + KsgCross::, Mirror::X, "Cross_false_ref_x" ); test_reflected!( test_reflected_cross_false_y, - Cross::, + KsgCross::, Mirror::Y, "Cross_false_ref_y" ); test_reflected!( test_reflected_cross_false_diag, - Cross::, + KsgCross::, Mirror::Diag, "Cross_false_ref_diag" ); test_reflected!( test_reflected_cross_false_offdiag, - Cross::, + KsgCross::, Mirror::OffDiag, "Cross_false_ref_offdiag" ); @@ -669,73 +649,73 @@ test_reflected!( // Cross reflections test_reflected!( test_reflected_cross_true_x, - Cross::, + KsgCross::, Mirror::X, "Cross_true_ref_x" ); test_reflected!( test_reflected_cross_true_y, - Cross::, + KsgCross::, Mirror::Y, "Cross_true_ref_y" ); test_reflected!( test_reflected_cross_true_diag, - Cross::, + KsgCross::, Mirror::Diag, "Cross_true_ref_diag" ); test_reflected!( test_reflected_cross_true_offdiag, - Cross::, + KsgCross::, Mirror::OffDiag, "Cross_true_ref_offdiag" ); // Turn reflections -test_reflected!(test_reflected_turn_x, Turn, Mirror::X, "Turn_ref_x"); -test_reflected!(test_reflected_turn_y, Turn, Mirror::Y, "Turn_ref_y"); +test_reflected!(test_reflected_turn_x, KsgTurn, Mirror::X, "Turn_ref_x"); +test_reflected!(test_reflected_turn_y, KsgTurn, Mirror::Y, "Turn_ref_y"); test_reflected!( test_reflected_turn_diag, - Turn, + KsgTurn, Mirror::Diag, "Turn_ref_diag" ); test_reflected!( test_reflected_turn_offdiag, - Turn, + KsgTurn, Mirror::OffDiag, "Turn_ref_offdiag" ); // WTurn reflections -test_reflected!(test_reflected_wturn_x, WTurn, Mirror::X, "WTurn_ref_x"); -test_reflected!(test_reflected_wturn_y, WTurn, Mirror::Y, "WTurn_ref_y"); +test_reflected!(test_reflected_wturn_x, KsgWTurn, Mirror::X, "WTurn_ref_x"); +test_reflected!(test_reflected_wturn_y, KsgWTurn, Mirror::Y, "WTurn_ref_y"); test_reflected!( test_reflected_wturn_diag, - WTurn, + KsgWTurn, Mirror::Diag, "WTurn_ref_diag" ); test_reflected!( test_reflected_wturn_offdiag, - WTurn, + KsgWTurn, Mirror::OffDiag, "WTurn_ref_offdiag" ); // Branch reflections -test_reflected!(test_reflected_branch_x, Branch, Mirror::X, "Branch_ref_x"); -test_reflected!(test_reflected_branch_y, Branch, Mirror::Y, "Branch_ref_y"); +test_reflected!(test_reflected_branch_x, KsgBranch, Mirror::X, "Branch_ref_x"); +test_reflected!(test_reflected_branch_y, KsgBranch, Mirror::Y, "Branch_ref_y"); test_reflected!( test_reflected_branch_diag, - Branch, + KsgBranch, Mirror::Diag, "Branch_ref_diag" ); test_reflected!( test_reflected_branch_offdiag, - Branch, + KsgBranch, Mirror::OffDiag, "Branch_ref_offdiag" ); @@ -743,25 +723,25 @@ test_reflected!( // BranchFix reflections test_reflected!( test_reflected_branchfix_x, - BranchFix, + KsgBranchFix, Mirror::X, "BranchFix_ref_x" ); test_reflected!( test_reflected_branchfix_y, - BranchFix, + KsgBranchFix, Mirror::Y, "BranchFix_ref_y" ); test_reflected!( test_reflected_branchfix_diag, - BranchFix, + KsgBranchFix, Mirror::Diag, "BranchFix_ref_diag" ); test_reflected!( test_reflected_branchfix_offdiag, - BranchFix, + KsgBranchFix, Mirror::OffDiag, "BranchFix_ref_offdiag" ); @@ -769,41 +749,41 @@ test_reflected!( // BranchFixB reflections test_reflected!( test_reflected_branchfixb_x, - BranchFixB, + KsgBranchFixB, Mirror::X, "BranchFixB_ref_x" ); test_reflected!( test_reflected_branchfixb_y, - BranchFixB, + KsgBranchFixB, Mirror::Y, "BranchFixB_ref_y" ); test_reflected!( test_reflected_branchfixb_diag, - BranchFixB, + KsgBranchFixB, Mirror::Diag, "BranchFixB_ref_diag" ); test_reflected!( test_reflected_branchfixb_offdiag, - BranchFixB, + KsgBranchFixB, Mirror::OffDiag, "BranchFixB_ref_offdiag" ); // TCon reflections -test_reflected!(test_reflected_tcon_x, TCon, Mirror::X, "TCon_ref_x"); -test_reflected!(test_reflected_tcon_y, TCon, Mirror::Y, "TCon_ref_y"); +test_reflected!(test_reflected_tcon_x, KsgTCon, Mirror::X, "TCon_ref_x"); +test_reflected!(test_reflected_tcon_y, KsgTCon, Mirror::Y, "TCon_ref_y"); test_reflected!( test_reflected_tcon_diag, - TCon, + KsgTCon, Mirror::Diag, "TCon_ref_diag" ); test_reflected!( test_reflected_tcon_offdiag, - TCon, + KsgTCon, Mirror::OffDiag, "TCon_ref_offdiag" ); @@ -811,25 +791,25 @@ test_reflected!( // TrivialTurn reflections test_reflected!( test_reflected_trivialturn_x, - TrivialTurn, + KsgTrivialTurn, Mirror::X, "TrivialTurn_ref_x" ); test_reflected!( test_reflected_trivialturn_y, - TrivialTurn, + KsgTrivialTurn, Mirror::Y, "TrivialTurn_ref_y" ); test_reflected!( test_reflected_trivialturn_diag, - TrivialTurn, + KsgTrivialTurn, Mirror::Diag, "TrivialTurn_ref_diag" ); test_reflected!( test_reflected_trivialturn_offdiag, - TrivialTurn, + KsgTrivialTurn, Mirror::OffDiag, "TrivialTurn_ref_offdiag" ); @@ -837,25 +817,25 @@ test_reflected!( // EndTurn reflections test_reflected!( test_reflected_endturn_x, - EndTurn, + KsgEndTurn, Mirror::X, "EndTurn_ref_x" ); test_reflected!( test_reflected_endturn_y, - EndTurn, + KsgEndTurn, Mirror::Y, "EndTurn_ref_y" ); test_reflected!( test_reflected_endturn_diag, - EndTurn, + KsgEndTurn, Mirror::Diag, "EndTurn_ref_diag" ); test_reflected!( test_reflected_endturn_offdiag, - EndTurn, + KsgEndTurn, Mirror::OffDiag, "EndTurn_ref_offdiag" ); @@ -863,25 +843,25 @@ test_reflected!( // DanglingLeg reflections test_reflected!( test_reflected_danglingleg_x, - DanglingLeg, + KsgDanglingLeg, Mirror::X, "DanglingLeg_ref_x" ); test_reflected!( test_reflected_danglingleg_y, - DanglingLeg, + KsgDanglingLeg, Mirror::Y, "DanglingLeg_ref_y" ); test_reflected!( test_reflected_danglingleg_diag, - DanglingLeg, + KsgDanglingLeg, Mirror::Diag, "DanglingLeg_ref_diag" ); test_reflected!( test_reflected_danglingleg_offdiag, - DanglingLeg, + KsgDanglingLeg, Mirror::OffDiag, "DanglingLeg_ref_offdiag" ); diff --git a/src/unit_tests/unitdiskmapping_algorithms/julia_comparison.rs b/src/unit_tests/unitdiskmapping_algorithms/julia_comparison.rs index a3e7aa7d..3d1605a3 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/julia_comparison.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/julia_comparison.rs @@ -5,7 +5,7 @@ //! - Weighted (square lattice with weights) //! - Triangular (triangular lattice with weights) -use crate::rules::unitdiskmapping::{map_graph_triangular_with_order, map_graph_with_order}; +use crate::rules::unitdiskmapping::{ksg, triangular}; use serde::Deserialize; use std::collections::HashSet; use std::fs; @@ -124,7 +124,7 @@ fn compare_square_unweighted(name: &str) { // Use Julia's vertex order to ensure consistent mapping let vertex_order = get_vertex_order(&julia); - let rust_result = map_graph_with_order(num_vertices, &edges, &vertex_order); + let rust_result = ksg::map_unweighted_with_order(num_vertices, &edges, &vertex_order); // Collect Rust grid nodes from copyline_locations (0-indexed) let rust_nodes: HashSet<(i32, i32)> = rust_result @@ -147,7 +147,7 @@ fn compare_square_unweighted(name: &str) { println!("\n=== {} (square/unweighted) ===", name); print_comparison( &julia, - &rust_result.grid_graph.size(), + &rust_result.grid_dimensions, rust_result.mis_overhead, &julia_nodes, &rust_nodes, @@ -159,7 +159,7 @@ fn compare_square_unweighted(name: &str) { // Assertions assert_eq!( julia.grid_size, - rust_result.grid_graph.size(), + rust_result.grid_dimensions, "{} square: Grid size mismatch", name ); @@ -273,7 +273,7 @@ fn compare_triangular(name: &str) { // Extract Julia's vertex order from copy_lines let vertex_order = get_vertex_order(&julia); - let rust_result = map_graph_triangular_with_order(num_vertices, &edges, &vertex_order); + let rust_result = triangular::map_weighted_with_order(num_vertices, &edges, &vertex_order); // Collect Rust grid nodes from copyline_locations_triangular (0-indexed) let rust_nodes: HashSet<(i32, i32)> = rust_result @@ -296,7 +296,7 @@ fn compare_triangular(name: &str) { println!("\n=== {} (triangular) ===", name); print_comparison( &julia, - &rust_result.grid_graph.size(), + &rust_result.grid_dimensions, rust_result.mis_overhead, &julia_nodes, &rust_nodes, @@ -396,7 +396,7 @@ fn compare_triangular(name: &str) { // Assertions assert_eq!( julia.grid_size, - rust_result.grid_graph.size(), + rust_result.grid_dimensions, "{} triangular: Grid size mismatch", name ); @@ -560,12 +560,12 @@ fn compare_connected_cells(name: &str) { // Run Rust mapping with Julia's vertex order let vertex_order = get_vertex_order(&julia); - let rust_result = map_graph_with_order(num_vertices, &edges, &vertex_order); + let rust_result = ksg::map_unweighted_with_order(num_vertices, &edges, &vertex_order); // Re-create the grid with connections to check Connected cell positions let mut grid = crate::rules::unitdiskmapping::MappingGrid::with_padding( - rust_result.grid_graph.size().0, - rust_result.grid_graph.size().1, + rust_result.grid_dimensions.0, + rust_result.grid_dimensions.1, rust_result.spacing, rust_result.padding, ); diff --git a/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs b/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs index 0db575f1..7cf980ea 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/map_graph.rs @@ -3,20 +3,20 @@ //! Tests square lattice mapping, MappingResult, and config_back. use super::common::{is_independent_set, solve_mis, solve_mis_config}; -use crate::rules::unitdiskmapping::{map_graph, map_graph_with_order, MappingResult}; -use crate::topology::{smallgraph, Graph, GridType}; +use crate::rules::unitdiskmapping::{ksg, GridKind, MappingResult}; +use crate::topology::smallgraph; // === Square Lattice Basic Tests === #[test] fn test_map_path_graph() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert!(result.mis_overhead >= 0); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original = result.map_config_back(&config); assert_eq!(original.len(), 3); } @@ -24,9 +24,9 @@ fn test_map_path_graph() { #[test] fn test_map_triangle_graph() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - assert!(result.grid_graph.num_vertices() >= 3); + assert!(result.positions.len() >= 3); assert!(result.mis_overhead >= 0); assert_eq!(result.lines.len(), 3); } @@ -34,45 +34,45 @@ fn test_map_triangle_graph() { #[test] fn test_map_star_graph() { let edges = vec![(0, 1), (0, 2), (0, 3)]; - let result = map_graph(4, &edges); + let result = ksg::map_unweighted(4, &edges); - assert!(result.grid_graph.num_vertices() > 4); + assert!(result.positions.len() > 4); assert_eq!(result.lines.len(), 4); } #[test] fn test_map_empty_graph() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert_eq!(result.lines.len(), 3); } #[test] fn test_map_single_edge() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); assert_eq!(result.lines.len(), 2); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] fn test_map_single_vertex() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph(1, &edges); + let result = ksg::map_unweighted(1, &edges); assert_eq!(result.lines.len(), 1); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] fn test_map_complete_k4() { let edges = vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]; - let result = map_graph(4, &edges); + let result = ksg::map_unweighted(4, &edges); - assert!(result.grid_graph.num_vertices() > 4); + assert!(result.positions.len() > 4); assert_eq!(result.lines.len(), 4); } @@ -80,24 +80,24 @@ fn test_map_complete_k4() { fn test_map_graph_with_custom_order() { let edges = vec![(0, 1), (1, 2)]; let order = vec![2, 1, 0]; - let result = map_graph_with_order(3, &edges, &order); + let result = ksg::map_unweighted_with_order(3, &edges, &order); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert_eq!(result.lines.len(), 3); } #[test] fn test_square_grid_type() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - assert!(matches!(result.grid_graph.grid_type(), GridType::Square)); + assert!(matches!(result.kind, GridKind::Kings)); } #[test] fn test_mapping_preserves_vertex_count() { let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4)]; - let result = map_graph(5, &edges); + let result = ksg::map_unweighted(5, &edges); assert_eq!(result.lines.len(), 5); @@ -116,7 +116,7 @@ fn test_mapping_preserves_vertex_count() { #[test] fn test_mapping_result_serialization() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); let json = serde_json::to_string(&result).unwrap(); let deserialized: MappingResult = serde_json::from_str(&json).unwrap(); @@ -128,9 +128,9 @@ fn test_mapping_result_serialization() { #[test] fn test_mapping_result_config_back_all_zeros() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original = result.map_config_back(&config); assert_eq!(original.len(), 3); @@ -142,9 +142,9 @@ fn test_mapping_result_config_back_all_zeros() { #[test] fn test_mapping_result_config_back_returns_correct_length() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original = result.map_config_back(&config); assert_eq!(original.len(), 3); @@ -154,10 +154,10 @@ fn test_mapping_result_config_back_returns_correct_length() { #[test] fn test_mapping_result_fields_populated() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); assert!(!result.lines.is_empty()); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert!(result.spacing > 0); assert!(result.padding > 0); } @@ -168,16 +168,16 @@ fn test_mapping_result_fields_populated() { fn test_disconnected_graph() { // Two disconnected edges let edges = vec![(0, 1), (2, 3)]; - let result = map_graph(4, &edges); + let result = ksg::map_unweighted(4, &edges); assert_eq!(result.lines.len(), 4); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] fn test_linear_chain() { let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4)]; - let result = map_graph(5, &edges); + let result = ksg::map_unweighted(5, &edges); assert_eq!(result.lines.len(), 5); } @@ -186,7 +186,7 @@ fn test_linear_chain() { fn test_cycle_graph() { // C5: pentagon let edges = vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]; - let result = map_graph(5, &edges); + let result = ksg::map_unweighted(5, &edges); assert_eq!(result.lines.len(), 5); } @@ -195,7 +195,7 @@ fn test_cycle_graph() { fn test_bipartite_graph() { // K2,3 let edges = vec![(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)]; - let result = map_graph(5, &edges); + let result = ksg::map_unweighted(5, &edges); assert_eq!(result.lines.len(), 5); } @@ -208,7 +208,7 @@ fn test_map_standard_graphs_square() { for name in graph_names { let (n, edges) = smallgraph(name).unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); assert_eq!( result.lines.len(), @@ -218,7 +218,7 @@ fn test_map_standard_graphs_square() { n ); assert!( - result.grid_graph.num_vertices() > 0, + !result.positions.is_empty(), "{}: should have grid nodes", name ); @@ -230,10 +230,10 @@ fn test_map_standard_graphs_square() { #[test] fn test_map_config_back_returns_valid_is() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let grid_config = solve_mis_config(result.grid_graph.num_vertices(), &grid_edges); + let grid_edges = result.edges(); + let grid_config = solve_mis_config(result.positions.len(), &grid_edges); let original_config = result.map_config_back(&grid_config); @@ -247,11 +247,11 @@ fn test_map_config_back_returns_valid_is() { fn test_mis_overhead_path_graph() { let edges = vec![(0, 1), (1, 2)]; let n = 3; - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges) as i32; - let grid_edges = result.grid_graph.edges().to_vec(); - let mapped_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges) as i32; + let grid_edges = result.edges(); + let mapped_mis = solve_mis(result.positions.len(), &grid_edges) as i32; let expected = original_mis + result.mis_overhead; @@ -269,11 +269,11 @@ fn test_mis_overhead_path_graph() { fn test_mis_overhead_triangle() { let edges = vec![(0, 1), (1, 2), (0, 2)]; let n = 3; - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges) as i32; - let grid_edges = result.grid_graph.edges().to_vec(); - let mapped_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges) as i32; + let grid_edges = result.edges(); + let mapped_mis = solve_mis(result.positions.len(), &grid_edges) as i32; let expected = original_mis + result.mis_overhead; @@ -290,11 +290,11 @@ fn test_mis_overhead_triangle() { #[test] fn test_mis_overhead_cubical() { let (n, edges) = smallgraph("cubical").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges) as i32; - let grid_edges = result.grid_graph.edges().to_vec(); - let mapped_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges) as i32; + let grid_edges = result.edges(); + let mapped_mis = solve_mis(result.positions.len(), &grid_edges) as i32; let expected = original_mis + result.mis_overhead; @@ -309,11 +309,11 @@ fn test_mis_overhead_cubical() { #[ignore] // tutte maps to 1232-vertex grid; ILP solving takes ~10s fn test_mis_overhead_tutte() { let (n, edges) = smallgraph("tutte").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges) as i32; - let grid_edges = result.grid_graph.edges().to_vec(); - let mapped_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges) as i32; + let grid_edges = result.edges(); + let mapped_mis = solve_mis(result.positions.len(), &grid_edges) as i32; let expected = original_mis + result.mis_overhead; @@ -358,11 +358,11 @@ fn test_map_config_back_standard_graphs() { for name in graph_names { let (n, edges) = smallgraph(name).unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Solve MIS on mapped graph - let grid_edges = result.grid_graph.edges().to_vec(); - let grid_config = solve_mis_config(result.grid_graph.num_vertices(), &grid_edges); + let grid_edges = result.edges(); + let grid_config = solve_mis_config(result.positions.len(), &grid_edges); // Extract original config using gadget traceback let original_config = result.map_config_back(&grid_config); @@ -390,9 +390,9 @@ fn test_map_config_back_standard_graphs() { #[test] fn test_map_config_back_via_centers_all_zeros() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original = result.map_config_back_via_centers(&config); assert_eq!(original.len(), 3); @@ -403,9 +403,9 @@ fn test_map_config_back_via_centers_all_zeros() { #[test] fn test_map_config_back_via_centers_triangle() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original = result.map_config_back_via_centers(&config); assert_eq!(original.len(), 3); @@ -414,10 +414,10 @@ fn test_map_config_back_via_centers_triangle() { #[test] fn test_map_config_back_via_centers_star() { let edges = vec![(0, 1), (0, 2), (0, 3)]; - let result = map_graph(4, &edges); + let result = ksg::map_unweighted(4, &edges); // Set all grid nodes to selected - let config = vec![1; result.grid_graph.num_vertices()]; + let config = vec![1; result.positions.len()]; let original = result.map_config_back_via_centers(&config); assert_eq!(original.len(), 4); @@ -427,9 +427,9 @@ fn test_map_config_back_via_centers_star() { fn test_map_config_back_consistency() { // Both methods should give reasonable results for the same input let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let via_regions = result.map_config_back(&config); let via_centers = result.map_config_back_via_centers(&config); @@ -448,17 +448,17 @@ fn test_large_graph_mapping() { let edges: Vec<(usize, usize)> = (0..9) .flat_map(|i| [(i, (i + 1) % 10), (i, (i + 3) % 10)]) .collect(); - let result = map_graph(10, &edges); + let result = ksg::map_unweighted(10, &edges); assert_eq!(result.lines.len(), 10); - assert!(result.grid_graph.num_vertices() > 10); + assert!(result.positions.len() > 10); } #[test] fn test_mapping_result_tape_populated() { // Triangle graph should generate crossings let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); // Tape may or may not have entries depending on crossings // Just verify it's accessible @@ -468,9 +468,9 @@ fn test_mapping_result_tape_populated() { #[test] fn test_grid_graph_edges() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let grid_edges = result.grid_graph.edges(); + let grid_edges = result.edges(); // Grid graph should have edges based on unit disk distance // Just verify edges are accessible let _edge_count = grid_edges.len(); @@ -479,11 +479,11 @@ fn test_grid_graph_edges() { #[test] fn test_grid_graph_nodes_have_weights() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - for node in result.grid_graph.nodes() { + for &weight in &result.node_weights { // All nodes should have positive weights - assert!(node.weight > 0, "Node weight should be positive"); + assert!(weight > 0, "Node weight should be positive"); } } diff --git a/src/unit_tests/unitdiskmapping_algorithms/mapping_result.rs b/src/unit_tests/unitdiskmapping_algorithms/mapping_result.rs index 9c0522a3..d3ebed65 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/mapping_result.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/mapping_result.rs @@ -1,16 +1,16 @@ //! Tests for MappingResult utility methods and unapply functionality. -use crate::rules::unitdiskmapping::{ksg, map_graph}; -use crate::topology::{smallgraph, Graph}; +use crate::rules::unitdiskmapping::ksg; +use crate::topology::smallgraph; // === MappingResult Utility Methods === #[test] fn test_mapping_result_grid_size() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); - let (rows, cols) = result.grid_size(); + let (rows, cols) = result.grid_dimensions; assert!(rows > 0, "Grid should have positive rows"); assert!(cols > 0, "Grid should have positive cols"); } @@ -18,7 +18,7 @@ fn test_mapping_result_grid_size() { #[test] fn test_mapping_result_num_original_vertices() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph(3, &edges); + let result = ksg::map_unweighted(3, &edges); assert_eq!(result.num_original_vertices(), 3); } @@ -26,9 +26,9 @@ fn test_mapping_result_num_original_vertices() { #[test] fn test_mapping_result_format_config() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - let (rows, cols) = result.grid_size(); + let (rows, cols) = result.grid_dimensions; let config: Vec> = vec![vec![0; cols]; rows]; let formatted = result.format_config(&config); @@ -45,9 +45,9 @@ fn test_mapping_result_format_config() { #[test] fn test_mapping_result_format_config_with_selected() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - let (rows, cols) = result.grid_size(); + let (rows, cols) = result.grid_dimensions; let mut config: Vec> = vec![vec![0; cols]; rows]; // Set some cells as selected @@ -66,9 +66,9 @@ fn test_mapping_result_format_config_with_selected() { #[test] fn test_mapping_result_format_config_flat() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![0; num_nodes]; let formatted = result.format_config_flat(&config); @@ -81,7 +81,7 @@ fn test_mapping_result_format_config_flat() { #[test] fn test_mapping_result_display() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); let display = format!("{}", result); assert!(!display.is_empty(), "Display should not be empty"); @@ -94,7 +94,7 @@ fn test_weighted_mapping_result_grid_size() { let edges = vec![(0, 1), (1, 2)]; let result = ksg::map_weighted(3, &edges); - let (rows, cols) = result.grid_size(); + let (rows, cols) = result.grid_dimensions; assert!(rows > 0, "Grid should have positive rows"); assert!(cols > 0, "Grid should have positive cols"); } @@ -112,7 +112,7 @@ fn test_weighted_mapping_result_format_config() { let edges = vec![(0, 1)]; let result = ksg::map_weighted(2, &edges); - let (rows, cols) = result.grid_size(); + let (rows, cols) = result.grid_dimensions; let config: Vec> = vec![vec![0; cols]; rows]; let formatted = result.format_config(&config); @@ -149,9 +149,9 @@ fn test_unapply_weighted_gadgets_empty_tape() { #[test] fn test_map_config_back_unweighted() { let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![0; num_nodes]; let original_config = result.map_config_back(&config); @@ -163,7 +163,7 @@ fn test_map_config_back_weighted() { let (n, edges) = smallgraph("diamond").unwrap(); let result = ksg::map_weighted(n, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![0; num_nodes]; let original_config = result.map_config_back(&config); @@ -180,11 +180,11 @@ fn test_full_pipeline_diamond_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Solve MIS on the grid graph - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); // Map config back to original graph @@ -202,10 +202,10 @@ fn test_full_pipeline_bull_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let original_config = result.map_config_back(&grid_config); @@ -221,10 +221,10 @@ fn test_full_pipeline_house_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = smallgraph("house").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let original_config = result.map_config_back(&grid_config); @@ -240,10 +240,10 @@ fn test_full_pipeline_petersen_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = smallgraph("petersen").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let original_config = result.map_config_back(&grid_config); @@ -261,12 +261,12 @@ fn test_full_pipeline_weighted_diamond() { let (n, edges) = smallgraph("diamond").unwrap(); let result = ksg::map_weighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); - // Get weights from the grid graph + // Get weights from the mapping result let weights: Vec = (0..num_grid) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -285,11 +285,11 @@ fn test_full_pipeline_weighted_bull() { let (n, edges) = smallgraph("bull").unwrap(); let result = ksg::map_weighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let weights: Vec = (0..num_grid) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -308,14 +308,14 @@ fn test_mis_size_preserved_diamond() { use super::common::solve_mis; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Get original MIS size let original_mis = solve_mis(n, &edges); // Get grid MIS size - let grid_edges = result.grid_graph.edges().to_vec(); - let grid_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges); + let grid_edges = result.edges(); + let grid_mis = solve_mis(result.positions.len(), &grid_edges); // Verify the formula: grid_mis = original_mis + overhead let expected_grid_mis = original_mis as i32 + result.mis_overhead; @@ -331,11 +331,11 @@ fn test_mis_size_preserved_bull() { use super::common::solve_mis; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let grid_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges); + let grid_edges = result.edges(); + let grid_mis = solve_mis(result.positions.len(), &grid_edges); let expected_grid_mis = original_mis as i32 + result.mis_overhead; assert_eq!(grid_mis as i32, expected_grid_mis); @@ -346,11 +346,11 @@ fn test_mis_size_preserved_house() { use super::common::solve_mis; let (n, edges) = smallgraph("house").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); let original_mis = solve_mis(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let grid_mis = solve_mis(result.grid_graph.num_vertices(), &grid_edges); + let grid_edges = result.edges(); + let grid_mis = solve_mis(result.positions.len(), &grid_edges); let expected_grid_mis = original_mis as i32 + result.mis_overhead; assert_eq!(grid_mis as i32, expected_grid_mis); @@ -361,16 +361,16 @@ fn test_mis_size_preserved_house() { #[test] fn test_full_pipeline_triangular_diamond() { use super::common::{is_independent_set, solve_weighted_mis_config}; - use crate::rules::unitdiskmapping::map_graph_triangular; + use crate::rules::unitdiskmapping::triangular; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let weights: Vec = (0..num_grid) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -385,16 +385,16 @@ fn test_full_pipeline_triangular_diamond() { #[test] fn test_full_pipeline_triangular_bull() { use super::common::{is_independent_set, solve_weighted_mis_config}; - use crate::rules::unitdiskmapping::map_graph_triangular; + use crate::rules::unitdiskmapping::triangular; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let weights: Vec = (0..num_grid) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -409,16 +409,16 @@ fn test_full_pipeline_triangular_bull() { #[test] fn test_full_pipeline_triangular_house() { use super::common::{is_independent_set, solve_weighted_mis_config}; - use crate::rules::unitdiskmapping::map_graph_triangular; + use crate::rules::unitdiskmapping::triangular; let (n, edges) = smallgraph("house").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let weights: Vec = (0..num_grid) - .map(|i| result.grid_graph.weight(i).copied().unwrap_or(1)) + .map(|i| result.node_weights.get(i).copied().unwrap_or(1)) .collect(); let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -435,14 +435,15 @@ fn test_full_pipeline_triangular_house() { #[test] fn test_apply_and_unapply_gadget() { use crate::rules::unitdiskmapping::{ - apply_gadget, unapply_gadget, CellState, MappingGrid, Pattern, Turn, + apply_gadget, unapply_gadget, CellState, MappingGrid, Pattern, }; + use crate::rules::unitdiskmapping::ksg::KsgTurn; // Create a small grid with spacing 4 let mut grid = MappingGrid::new(10, 10, 4); // Set up some occupied cells for a Turn gadget - let turn = Turn; + let turn = KsgTurn; let (rows, cols) = turn.size(); // Initialize with the source pattern at position (2, 2) @@ -466,10 +467,11 @@ fn test_apply_and_unapply_gadget() { #[test] fn test_apply_gadget_at_various_positions() { - use crate::rules::unitdiskmapping::{apply_gadget, CellState, MappingGrid, Pattern, Turn}; + use crate::rules::unitdiskmapping::{apply_gadget, CellState, MappingGrid, Pattern}; + use crate::rules::unitdiskmapping::ksg::KsgTurn; let mut grid = MappingGrid::new(20, 20, 4); - let turn = Turn; + let turn = KsgTurn; let (rows, cols) = turn.size(); // Apply at position (0, 0) @@ -500,11 +502,11 @@ fn test_extracted_mis_equals_original() { use super::common::{solve_mis, solve_mis_config}; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Solve MIS on grid - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); // Map back @@ -526,10 +528,10 @@ fn test_extracted_mis_equals_original_bull() { use super::common::{solve_mis, solve_mis_config}; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let original_config = result.map_config_back(&grid_config); @@ -542,26 +544,23 @@ fn test_extracted_mis_equals_original_bull() { // === Grid Graph Format Tests === #[test] -fn test_grid_graph_format_with_config() { +fn test_grid_graph_format_display() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - let formatted = result.grid_graph.format_with_config(None, false); + let formatted = format!("{}", result); assert!(!formatted.is_empty()); - - let formatted_with_coords = result.grid_graph.format_with_config(None, true); - assert!(!formatted_with_coords.is_empty()); } #[test] fn test_grid_graph_format_with_some_config() { let edges = vec![(0, 1)]; - let result = map_graph(2, &edges); + let result = ksg::map_unweighted(2, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![1; num_nodes]; - let formatted = result.grid_graph.format_with_config(Some(&config), false); + let formatted = result.format_config_flat(&config); assert!(!formatted.is_empty()); } @@ -573,9 +572,9 @@ fn test_all_standard_graphs_unapply() { for name in graph_names { let (n, edges) = smallgraph(name).unwrap(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![0; num_nodes]; let original = result.map_config_back(&config); @@ -596,7 +595,7 @@ fn test_all_standard_graphs_weighted_unapply() { let (n, edges) = smallgraph(name).unwrap(); let result = ksg::map_weighted(n, &edges); - let num_nodes = result.grid_graph.num_vertices(); + let num_nodes = result.positions.len(); let config: Vec = vec![0; num_nodes]; let original = result.map_config_back(&config); @@ -646,11 +645,11 @@ fn test_interface_k23_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = k23_graph(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Check MIS size preservation: mis_overhead + original_mis = mapped_mis - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let grid_mis: usize = grid_config.iter().sum(); @@ -682,11 +681,11 @@ fn test_interface_empty_graph_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = empty_graph(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // For empty graph, all vertices can be selected - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let grid_mis: usize = grid_config.iter().sum(); @@ -717,11 +716,11 @@ fn test_interface_path_graph_unweighted() { use super::common::{is_independent_set, solve_mis_config}; let (n, edges) = path_graph(); - let result = map_graph(n, &edges); + let result = ksg::map_unweighted(n, &edges); // Check MIS size preservation - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let grid_mis: usize = grid_config.iter().sum(); @@ -752,8 +751,8 @@ fn test_interface_k23_weighted() { let result = ksg::map_weighted(n, &edges); // Check MIS size preservation - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); // Check map_config_back produces valid IS @@ -772,7 +771,7 @@ fn test_interface_empty_graph_weighted() { let result = ksg::map_weighted(n, &edges); // For empty graph with weighted mapping - let num_grid = result.grid_graph.num_vertices(); + let num_grid = result.positions.len(); // All zeros config is always valid let grid_config: Vec = vec![0; num_grid]; @@ -791,8 +790,8 @@ fn test_interface_path_graph_weighted() { let result = ksg::map_weighted(n, &edges); // Check map_config_back - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let grid_config = solve_mis_config(num_grid, &grid_edges); let mapped_back = result.map_config_back(&grid_config); diff --git a/src/unit_tests/unitdiskmapping_algorithms/triangular.rs b/src/unit_tests/unitdiskmapping_algorithms/triangular.rs index 127c883d..3a8d4fea 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/triangular.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/triangular.rs @@ -1,10 +1,8 @@ //! Tests for triangular lattice mapping (src/rules/mapping/triangular.rs). use super::common::solve_weighted_grid_mis; -use crate::rules::unitdiskmapping::{ - map_graph_triangular, map_graph_triangular_with_order, trace_centers, MappingResult, -}; -use crate::topology::{smallgraph, Graph}; +use crate::rules::unitdiskmapping::{trace_centers, triangular, MappingResult}; +use crate::topology::smallgraph; use std::collections::HashMap; // === Basic Triangular Mapping Tests === @@ -12,9 +10,9 @@ use std::collections::HashMap; #[test] fn test_triangular_path_graph() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert!(result.mis_overhead >= 0); assert_eq!(result.lines.len(), 3); } @@ -22,27 +20,27 @@ fn test_triangular_path_graph() { #[test] fn test_triangular_complete_k4() { let edges = vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]; - let result = map_graph_triangular(4, &edges); + let result = triangular::map_weighted(4, &edges); - assert!(result.grid_graph.num_vertices() > 4); + assert!(result.positions.len() > 4); assert_eq!(result.lines.len(), 4); } #[test] fn test_triangular_single_vertex() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph_triangular(1, &edges); + let result = triangular::map_weighted(1, &edges); assert_eq!(result.lines.len(), 1); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); } #[test] fn test_triangular_empty_graph() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert_eq!(result.lines.len(), 3); } @@ -50,18 +48,18 @@ fn test_triangular_empty_graph() { fn test_triangular_with_custom_order() { let edges = vec![(0, 1), (1, 2)]; let order = vec![2, 1, 0]; - let result = map_graph_triangular_with_order(3, &edges, &order); + let result = triangular::map_weighted_with_order(3, &edges, &order); - assert!(result.grid_graph.num_vertices() > 0); + assert!(!result.positions.is_empty()); assert_eq!(result.lines.len(), 3); } #[test] fn test_triangular_star_graph() { let edges = vec![(0, 1), (0, 2), (0, 3)]; - let result = map_graph_triangular(4, &edges); + let result = triangular::map_weighted(4, &edges); - assert!(result.grid_graph.num_vertices() > 4); + assert!(result.positions.len() > 4); assert_eq!(result.lines.len(), 4); } @@ -69,13 +67,13 @@ fn test_triangular_star_graph() { #[should_panic] fn test_triangular_zero_vertices_panics() { let edges: Vec<(usize, usize)> = vec![]; - let _ = map_graph_triangular(0, &edges); + let _ = triangular::map_weighted(0, &edges); } #[test] fn test_triangular_offset_setting() { let edges = vec![(0, 1)]; - let result = map_graph_triangular(2, &edges); + let result = triangular::map_weighted(2, &edges); // Triangular mode uses spacing=6, padding=2 assert_eq!(result.spacing, 6); @@ -85,7 +83,7 @@ fn test_triangular_offset_setting() { #[test] fn test_triangular_mapping_result_serialization() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let json = serde_json::to_string(&result).unwrap(); let deserialized: MappingResult = serde_json::from_str(&json).unwrap(); @@ -102,7 +100,7 @@ fn test_map_standard_graphs_triangular() { for name in graph_names { let (n, edges) = smallgraph(name).unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); assert_eq!( result.lines.len(), @@ -112,7 +110,7 @@ fn test_map_standard_graphs_triangular() { n ); assert!( - result.grid_graph.num_vertices() > 0, + !result.positions.is_empty(), "{}: should have grid nodes", name ); @@ -155,7 +153,7 @@ fn verify_mapping_matches_julia(name: &str) -> bool { // Use Julia's vertex order to ensure consistent mapping let vertex_order = get_julia_vertex_order(name).unwrap_or_else(|| (0..n).collect()); - let result = map_graph_triangular_with_order(n, &edges, &vertex_order); + let result = triangular::map_weighted_with_order(n, &edges, &vertex_order); // Load Julia's trace data let julia_path = format!( @@ -174,11 +172,11 @@ fn verify_mapping_matches_julia(name: &str) -> bool { // Compare node count let julia_nodes = julia_data["num_grid_nodes"].as_u64().unwrap() as usize; - if result.grid_graph.num_vertices() != julia_nodes { + if result.positions.len() != julia_nodes { eprintln!( "{}: node count mismatch - Rust={}, Julia={}", name, - result.grid_graph.num_vertices(), + result.positions.len(), julia_nodes ); return false; @@ -196,7 +194,7 @@ fn verify_mapping_matches_julia(name: &str) -> bool { // Compare edge count if let Some(julia_edges) = julia_data["num_grid_edges"].as_u64() { - let rust_edges = result.grid_graph.num_edges(); + let rust_edges = result.num_edges(); if rust_edges != julia_edges as usize { eprintln!( "{}: edge count mismatch - Rust={}, Julia={}", @@ -237,7 +235,7 @@ fn verify_mapping_matches_julia(name: &str) -> bool { fn test_triangular_mis_overhead_path_graph() { let edges = vec![(0, 1), (1, 2)]; let n = 3; - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); let mapped_mis = solve_weighted_grid_mis(&result) as i32; @@ -302,7 +300,7 @@ fn test_triangular_mapping_tutte() { #[test] fn test_trace_centers_single_vertex() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph_triangular(1, &edges); + let result = triangular::map_weighted(1, &edges); let centers = trace_centers(&result); assert_eq!(centers.len(), 1); @@ -311,7 +309,7 @@ fn test_trace_centers_single_vertex() { #[test] fn test_trace_centers_path_graph() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let centers = trace_centers(&result); assert_eq!(centers.len(), 3); @@ -326,7 +324,7 @@ fn test_trace_centers_path_graph() { #[test] fn test_trace_centers_triangle() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let centers = trace_centers(&result); assert_eq!(centers.len(), 3); @@ -345,8 +343,6 @@ fn test_trace_centers_triangle() { fn test_triangular_map_config_back_standard_graphs() { use super::common::{is_independent_set, solve_mis, solve_weighted_mis_config}; use crate::rules::unitdiskmapping::map_weights; - use crate::topology::Graph; - // All standard graphs (excluding tutte/karate which are slow) let graph_names = [ "bull", @@ -376,7 +372,7 @@ fn test_triangular_map_config_back_standard_graphs() { // Use Julia's vertex order if available let vertex_order = get_julia_vertex_order(name).unwrap_or_else(|| (0..n).collect()); - let result = map_graph_triangular_with_order(n, &edges, &vertex_order); + let result = triangular::map_weighted_with_order(n, &edges, &vertex_order); // Follow Julia's approach: source weights of 0.2 for each vertex let source_weights: Vec = vec![0.2; n]; @@ -390,8 +386,8 @@ fn test_triangular_map_config_back_standard_graphs() { .map(|&w| (w * 10.0).round() as i32) .collect(); - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); // Solve weighted MIS on grid let grid_config = solve_weighted_mis_config(num_grid, &grid_edges, &weights); @@ -399,8 +395,8 @@ fn test_triangular_map_config_back_standard_graphs() { // Use triangular-specific trace_centers (not the KSG version) // Build position to node index map let mut pos_to_idx: HashMap<(usize, usize), usize> = HashMap::new(); - for (idx, node) in result.grid_graph.nodes().iter().enumerate() { - if let (Ok(row), Ok(col)) = (usize::try_from(node.row), usize::try_from(node.col)) { + for (idx, &(row, col)) in result.positions.iter().enumerate() { + if let (Ok(row), Ok(col)) = (usize::try_from(row), usize::try_from(col)) { pos_to_idx.insert((row, col), idx); } } diff --git a/src/unit_tests/unitdiskmapping_algorithms/weighted.rs b/src/unit_tests/unitdiskmapping_algorithms/weighted.rs index ed5b474e..838cb372 100644 --- a/src/unit_tests/unitdiskmapping_algorithms/weighted.rs +++ b/src/unit_tests/unitdiskmapping_algorithms/weighted.rs @@ -1,17 +1,14 @@ //! Tests for weighted mode functionality (src/rules/mapping/weighted.rs). use crate::rules::unitdiskmapping::{ - copyline_weighted_locations_triangular, map_graph_triangular, map_weights, trace_centers, - CopyLine, + copyline_weighted_locations_triangular, ksg, map_weights, trace_centers, triangular, CopyLine, }; -use crate::topology::Graph; - // === Trace Centers Tests === #[test] fn test_trace_centers_returns_correct_count() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let centers = trace_centers(&result); assert_eq!(centers.len(), 3); @@ -20,7 +17,7 @@ fn test_trace_centers_returns_correct_count() { #[test] fn test_trace_centers_positive_coordinates() { let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let centers = trace_centers(&result); for (i, &(row, col)) in centers.iter().enumerate() { @@ -32,7 +29,7 @@ fn test_trace_centers_positive_coordinates() { #[test] fn test_trace_centers_single_vertex() { let edges: Vec<(usize, usize)> = vec![]; - let result = map_graph_triangular(1, &edges); + let result = triangular::map_weighted(1, &edges); let centers = trace_centers(&result); assert_eq!(centers.len(), 1); @@ -43,7 +40,7 @@ fn test_trace_centers_single_vertex() { #[test] fn test_map_weights_uniform() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); // Use uniform weights (all 0.5) let weights = vec![0.5, 0.5, 0.5]; @@ -56,13 +53,13 @@ fn test_map_weights_uniform() { ); // Mapped should have one weight per grid node - assert_eq!(mapped.len(), result.grid_graph.num_vertices()); + assert_eq!(mapped.len(), result.positions.len()); } #[test] fn test_map_weights_zero() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let weights = vec![0.0, 0.0, 0.0]; let mapped = map_weights(&result, &weights); @@ -75,7 +72,7 @@ fn test_map_weights_zero() { #[test] fn test_map_weights_one() { let edges = vec![(0, 1), (1, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let weights = vec![1.0, 1.0, 1.0]; let mapped = map_weights(&result, &weights); @@ -85,10 +82,9 @@ fn test_map_weights_one() { // Mapped weights should equal base weights plus original weights at centers let base_total: f64 = result - .grid_graph - .nodes() + .node_weights .iter() - .map(|n| n.weight as f64) + .map(|&w| w as f64) .sum(); let original_total: f64 = weights.iter().sum(); let mapped_total: f64 = mapped.iter().sum(); @@ -109,7 +105,7 @@ fn test_map_weights_one() { #[should_panic] fn test_map_weights_invalid_negative() { let edges = vec![(0, 1)]; - let result = map_graph_triangular(2, &edges); + let result = triangular::map_weighted(2, &edges); let weights = vec![-0.5, 0.5]; let _ = map_weights(&result, &weights); @@ -119,7 +115,7 @@ fn test_map_weights_invalid_negative() { #[should_panic] fn test_map_weights_invalid_over_one() { let edges = vec![(0, 1)]; - let result = map_graph_triangular(2, &edges); + let result = triangular::map_weighted(2, &edges); let weights = vec![1.5, 0.5]; let _ = map_weights(&result, &weights); @@ -129,7 +125,7 @@ fn test_map_weights_invalid_over_one() { #[should_panic] fn test_map_weights_wrong_length() { let edges = vec![(0, 1)]; - let result = map_graph_triangular(2, &edges); + let result = triangular::map_weighted(2, &edges); let weights = vec![0.5]; // Wrong length let _ = map_weights(&result, &weights); @@ -142,14 +138,14 @@ fn test_triangular_weighted_interface() { use crate::topology::smallgraph; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); // Test with uniform weights let ws = vec![0.5; n]; let grid_weights = map_weights(&result, &ws); // Should produce valid weights for all grid nodes - assert_eq!(grid_weights.len(), result.grid_graph.num_vertices()); + assert_eq!(grid_weights.len(), result.positions.len()); assert!(grid_weights.iter().all(|&w| w > 0.0)); } @@ -158,17 +154,17 @@ fn test_triangular_interface_full() { use crate::topology::smallgraph; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); // Uniform weights in [0, 1] let ws = vec![0.3; n]; let grid_weights = map_weights(&result, &ws); - assert_eq!(grid_weights.len(), result.grid_graph.num_vertices()); + assert_eq!(grid_weights.len(), result.positions.len()); assert!(grid_weights.iter().all(|&w| w >= 0.0)); // Test map_config_back - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let original_config = result.map_config_back(&config); assert_eq!(original_config.len(), n); @@ -223,9 +219,7 @@ fn test_triangular_copyline_weight_invariant() { #[test] fn test_weighted_gadgets_weight_conservation() { // For each weighted gadget, verify weight sums are consistent with MIS properties - use crate::rules::unitdiskmapping::triangular_weighted_ruleset; - - let ruleset = triangular_weighted_ruleset(); + let ruleset = triangular::weighted_ruleset(); for gadget in &ruleset { let source_sum: i32 = gadget.source_weights().iter().sum(); let mapped_sum: i32 = gadget.mapped_weights().iter().sum(); @@ -252,9 +246,7 @@ fn test_weighted_gadgets_weight_conservation() { #[test] fn test_weighted_gadgets_positive_weights() { // All individual weights should be positive - use crate::rules::unitdiskmapping::triangular_weighted_ruleset; - - let ruleset = triangular_weighted_ruleset(); + let ruleset = triangular::weighted_ruleset(); for gadget in &ruleset { for &w in gadget.source_weights() { assert!(w > 0, "Source weights should be positive, got {}", w); @@ -269,14 +261,13 @@ fn test_weighted_gadgets_positive_weights() { #[test] fn test_map_config_back_extracts_valid_is_triangular() { - use crate::rules::unitdiskmapping::map_graph_triangular; - use crate::topology::{smallgraph, Graph}; + use crate::topology::smallgraph; let (n, edges) = smallgraph("bull").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); // Get all zeros config - let config = vec![0; result.grid_graph.num_vertices()]; + let config = vec![0; result.positions.len()]; let extracted = result.map_config_back(&config); // All zeros should extract to all zeros @@ -288,17 +279,16 @@ fn test_map_config_back_extracts_valid_is_triangular() { fn test_map_weights_preserves_total_weight() { // map_weights should add original weights to base weights let edges = vec![(0, 1), (1, 2), (0, 2)]; - let result = map_graph_triangular(3, &edges); + let result = triangular::map_weighted(3, &edges); let original_weights = vec![0.5, 0.3, 0.7]; let mapped = map_weights(&result, &original_weights); // Sum of mapped weights should be base_sum + original_sum let base_sum: f64 = result - .grid_graph - .nodes() + .node_weights .iter() - .map(|n| n.weight as f64) + .map(|&w| w as f64) .sum(); let original_sum: f64 = original_weights.iter().sum(); let mapped_sum: f64 = mapped.iter().sum(); @@ -319,7 +309,7 @@ fn test_trace_centers_consistency_with_config_back() { use crate::topology::smallgraph; let (n, edges) = smallgraph("diamond").unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); // Get centers let centers = trace_centers(&result); @@ -328,17 +318,15 @@ fn test_trace_centers_consistency_with_config_back() { // Each center should be within grid bounds let (rows, cols) = { let max_row = result - .grid_graph - .nodes() + .positions .iter() - .map(|n| n.row) + .map(|&(r, _)| r) .max() .unwrap_or(0); let max_col = result - .grid_graph - .nodes() + .positions .iter() - .map(|n| n.col) + .map(|&(_, c)| c) .max() .unwrap_or(0); (max_row as usize + 1, max_col as usize + 1) @@ -370,9 +358,8 @@ fn test_trace_centers_consistency_with_config_back() { #[test] fn test_square_gadget_trivial_turn_weights() { use crate::rules::unitdiskmapping::Pattern; - use crate::rules::unitdiskmapping::TrivialTurn; - let trivial_turn = TrivialTurn; + let trivial_turn = ksg::KsgTrivialTurn; let source_weights = trivial_turn.source_weights(); let mapped_weights = trivial_turn.mapped_weights(); @@ -411,10 +398,9 @@ fn test_square_gadget_trivial_turn_weights() { #[test] fn test_square_gadget_endturn_weights() { - use crate::rules::unitdiskmapping::EndTurn; use crate::rules::unitdiskmapping::Pattern; - let endturn = EndTurn; + let endturn = ksg::KsgEndTurn; let source_weights = endturn.source_weights(); let mapped_weights = endturn.mapped_weights(); @@ -450,9 +436,8 @@ fn test_square_gadget_endturn_weights() { #[test] fn test_square_gadget_tcon_weights() { use crate::rules::unitdiskmapping::Pattern; - use crate::rules::unitdiskmapping::TCon; - let tcon = TCon; + let tcon = ksg::KsgTCon; let source_weights = tcon.source_weights(); let mapped_weights = tcon.mapped_weights(); @@ -499,10 +484,9 @@ fn test_square_gadget_tcon_weights() { #[test] fn test_square_gadget_branchfixb_weights() { - use crate::rules::unitdiskmapping::BranchFixB; use crate::rules::unitdiskmapping::Pattern; - let branchfixb = BranchFixB; + let branchfixb = ksg::KsgBranchFixB; let source_weights = branchfixb.source_weights(); let mapped_weights = branchfixb.mapped_weights(); @@ -542,10 +526,9 @@ fn test_square_gadget_branchfixb_weights() { #[test] fn test_square_gadget_branch_weights() { - use crate::rules::unitdiskmapping::Branch; use crate::rules::unitdiskmapping::Pattern; - let branch = Branch; + let branch = ksg::KsgBranch; let source_weights = branch.source_weights(); let mapped_weights = branch.mapped_weights(); @@ -576,38 +559,35 @@ fn test_square_gadget_branch_weights() { #[test] fn test_square_gadget_default_weights_cross_false() { - use crate::rules::unitdiskmapping::Cross; use crate::rules::unitdiskmapping::Pattern; - let cross = Cross::; + let cross = ksg::KsgCross::; for &w in &cross.source_weights() { - assert_eq!(w, 2, "Cross source weights should all be 2"); + assert_eq!(w, 2, "KsgCross source weights should all be 2"); } for &w in &cross.mapped_weights() { - assert_eq!(w, 2, "Cross mapped weights should all be 2"); + assert_eq!(w, 2, "KsgCross mapped weights should all be 2"); } } #[test] fn test_square_gadget_default_weights_cross_true() { - use crate::rules::unitdiskmapping::Cross; use crate::rules::unitdiskmapping::Pattern; - let cross = Cross::; + let cross = ksg::KsgCross::; for &w in &cross.source_weights() { - assert_eq!(w, 2, "Cross source weights should all be 2"); + assert_eq!(w, 2, "KsgCross source weights should all be 2"); } for &w in &cross.mapped_weights() { - assert_eq!(w, 2, "Cross mapped weights should all be 2"); + assert_eq!(w, 2, "KsgCross mapped weights should all be 2"); } } #[test] fn test_square_gadget_default_weights_turn() { use crate::rules::unitdiskmapping::Pattern; - use crate::rules::unitdiskmapping::Turn; - let turn = Turn; + let turn = ksg::KsgTurn; for &w in &turn.source_weights() { assert_eq!(w, 2, "Turn source weights should all be 2"); } @@ -619,9 +599,8 @@ fn test_square_gadget_default_weights_turn() { #[test] fn test_square_gadget_default_weights_wturn() { use crate::rules::unitdiskmapping::Pattern; - use crate::rules::unitdiskmapping::WTurn; - let wturn = WTurn; + let wturn = ksg::KsgWTurn; for &w in &wturn.source_weights() { assert_eq!(w, 2, "WTurn source weights should all be 2"); } @@ -632,10 +611,9 @@ fn test_square_gadget_default_weights_wturn() { #[test] fn test_square_gadget_default_weights_branchfix() { - use crate::rules::unitdiskmapping::BranchFix; use crate::rules::unitdiskmapping::Pattern; - let branchfix = BranchFix; + let branchfix = ksg::KsgBranchFix; for &w in &branchfix.source_weights() { assert_eq!(w, 2, "BranchFix source weights should all be 2"); } @@ -646,10 +624,9 @@ fn test_square_gadget_default_weights_branchfix() { #[test] fn test_square_danglinleg_weights() { - use crate::rules::unitdiskmapping::DanglingLeg; use crate::rules::unitdiskmapping::Pattern; - let danglinleg = DanglingLeg; + let danglinleg = ksg::KsgDanglingLeg; let source_weights = danglinleg.source_weights(); let mapped_weights = danglinleg.mapped_weights(); @@ -699,9 +676,8 @@ fn test_square_danglinleg_weights() { fn test_weighted_map_config_back_standard_graphs() { use super::common::{is_independent_set, solve_mis}; use crate::models::optimization::{LinearConstraint, ObjectiveSense, ILP}; - use crate::rules::unitdiskmapping::{map_graph_triangular, map_weights}; use crate::solvers::ILPSolver; - use crate::topology::{smallgraph, Graph}; + use crate::topology::smallgraph; // All standard graphs (excluding tutte/karate which are slow) let graph_names = [ @@ -729,7 +705,7 @@ fn test_weighted_map_config_back_standard_graphs() { for name in graph_names { let (n, edges) = smallgraph(name).unwrap(); - let result = map_graph_triangular(n, &edges); + let result = triangular::map_weighted(n, &edges); // Follow Julia's approach: source weights of 0.2 for each vertex let source_weights: Vec = vec![0.2; n]; @@ -738,8 +714,8 @@ fn test_weighted_map_config_back_standard_graphs() { let mapped_weights = map_weights(&result, &source_weights); // Solve weighted MIS with ILP - let grid_edges = result.grid_graph.edges().to_vec(); - let num_grid = result.grid_graph.num_vertices(); + let grid_edges = result.edges(); + let num_grid = result.positions.len(); let constraints: Vec = grid_edges .iter() @@ -763,8 +739,8 @@ fn test_weighted_map_config_back_standard_graphs() { // Build position to node index map let mut pos_to_idx: std::collections::HashMap<(usize, usize), usize> = std::collections::HashMap::new(); - for (idx, node) in result.grid_graph.nodes().iter().enumerate() { - if let (Ok(row), Ok(col)) = (usize::try_from(node.row), usize::try_from(node.col)) { + for (idx, &(row, col)) in result.positions.iter().enumerate() { + if let (Ok(row), Ok(col)) = (usize::try_from(row), usize::try_from(col)) { pos_to_idx.insert((row, col), idx); } } diff --git a/src/unit_tests/variant.rs b/src/unit_tests/variant.rs index 35d1c0dd..bd18f9a9 100644 --- a/src/unit_tests/variant.rs +++ b/src/unit_tests/variant.rs @@ -1,31 +1,93 @@ -use super::*; +use crate::variant::{CastToParent, KValue, VariantParam, VariantTypeEntry}; + +// Test types for the new system +#[derive(Clone, Debug)] +struct TestRoot; +#[derive(Clone, Debug)] +struct TestChild; + +impl_variant_param!(TestRoot, "test_cat"); +impl_variant_param!(TestChild, "test_cat", parent: TestRoot, cast: |_| TestRoot); #[test] -fn test_short_type_name_primitive() { - assert_eq!(short_type_name::(), "i32"); - assert_eq!(short_type_name::(), "f64"); +fn test_variant_param_root() { + assert_eq!(TestRoot::CATEGORY, "test_cat"); + assert_eq!(TestRoot::VALUE, "TestRoot"); + assert_eq!(TestRoot::PARENT_VALUE, None); } #[test] -fn test_short_type_name_struct() { - struct MyStruct; - assert_eq!(short_type_name::(), "MyStruct"); +fn test_variant_param_child() { + assert_eq!(TestChild::CATEGORY, "test_cat"); + assert_eq!(TestChild::VALUE, "TestChild"); + assert_eq!(TestChild::PARENT_VALUE, Some("TestRoot")); } #[test] -fn test_const_usize_str() { - assert_eq!(const_usize_str::<1>(), "1"); - assert_eq!(const_usize_str::<2>(), "2"); - assert_eq!(const_usize_str::<3>(), "3"); - assert_eq!(const_usize_str::<4>(), "4"); - assert_eq!(const_usize_str::<5>(), "5"); - assert_eq!(const_usize_str::<6>(), "6"); - assert_eq!(const_usize_str::<7>(), "7"); - assert_eq!(const_usize_str::<8>(), "8"); - assert_eq!(const_usize_str::<9>(), "9"); - assert_eq!(const_usize_str::<10>(), "10"); - assert_eq!(const_usize_str::<11>(), "N"); - assert_eq!(const_usize_str::<100>(), "N"); +fn test_cast_to_parent() { + let child = TestChild; + let _parent: TestRoot = child.cast_to_parent(); +} + +#[test] +fn test_variant_type_entry_registered() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "test_cat") + .collect(); + assert!(entries + .iter() + .any(|e| e.value == "TestRoot" && e.parent.is_none())); + assert!(entries + .iter() + .any(|e| e.value == "TestChild" && e.parent == Some("TestRoot"))); +} + +#[derive(Clone, Debug)] +struct TestKRoot; +#[derive(Clone, Debug)] +struct TestKChild; + +impl_variant_param!(TestKRoot, "test_k", k: None); +impl_variant_param!(TestKChild, "test_k", parent: TestKRoot, cast: |_| TestKRoot, k: Some(3)); + +#[test] +fn test_kvalue_via_macro_root() { + assert_eq!(TestKRoot::CATEGORY, "test_k"); + assert_eq!(TestKRoot::VALUE, "TestKRoot"); + assert_eq!(TestKRoot::PARENT_VALUE, None); + assert_eq!(TestKRoot::K, None); +} + +#[test] +fn test_kvalue_via_macro_child() { + assert_eq!(TestKChild::CATEGORY, "test_k"); + assert_eq!(TestKChild::VALUE, "TestKChild"); + assert_eq!(TestKChild::PARENT_VALUE, Some("TestKRoot")); + assert_eq!(TestKChild::K, Some(3)); +} + +#[test] +fn test_variant_params_macro_empty() { + let v: Vec<(&str, &str)> = variant_params![]; + assert!(v.is_empty()); +} + +#[test] +fn test_variant_params_macro_single() { + fn check() -> Vec<(&'static str, &'static str)> { + variant_params![T] + } + let v = check::(); + assert_eq!(v, vec![("test_cat", "TestRoot")]); +} + +#[test] +fn test_variant_params_macro_multiple() { + fn check() -> Vec<(&'static str, &'static str)> { + variant_params![A, B] + } + let v = check::(); + assert_eq!(v, vec![("test_cat", "TestRoot"), ("test_cat", "TestChild")]); } #[test] @@ -75,9 +137,9 @@ fn test_variant_for_problems() { // Note: f64 variants removed because SolutionSize now requires Ord // Test KColoring (has K and graph parameters) - let v = KColoring::<3, SimpleGraph>::variant(); + let v = KColoring::::variant(); assert_eq!(v.len(), 2); - assert_eq!(v[0], ("k", "3")); + assert_eq!(v[0], ("k", "K3")); assert_eq!(v[1], ("graph", "SimpleGraph")); // Test MaximalIS @@ -94,10 +156,10 @@ fn test_variant_for_problems() { let v = Satisfiability::variant(); assert_eq!(v.len(), 0); - // Test KSatisfiability (const K parameter only) - let v = KSatisfiability::<3>::variant(); + // Test KSatisfiability (K type parameter only) + let v = KSatisfiability::::variant(); assert_eq!(v.len(), 1); - assert_eq!(v[0], ("k", "3")); + assert_eq!(v[0], ("k", "K3")); // Test MaximumSetPacking (weight parameter only) let v = MaximumSetPacking::::variant(); @@ -142,3 +204,185 @@ fn test_variant_for_problems() { let v = PaintShop::variant(); assert_eq!(v.len(), 0); } + +// --- KValue concrete type tests --- + +use crate::variant::{K1, K2, K3, K4, K5, KN}; + +#[test] +fn test_kvalue_k1() { + assert_eq!(K1::CATEGORY, "k"); + assert_eq!(K1::VALUE, "K1"); + assert_eq!(K1::PARENT_VALUE, Some("KN")); + assert_eq!(K1::K, Some(1)); +} + +#[test] +fn test_kvalue_k2() { + assert_eq!(K2::CATEGORY, "k"); + assert_eq!(K2::VALUE, "K2"); + assert_eq!(K2::PARENT_VALUE, Some("KN")); + assert_eq!(K2::K, Some(2)); +} + +#[test] +fn test_kvalue_k3() { + assert_eq!(K3::CATEGORY, "k"); + assert_eq!(K3::VALUE, "K3"); + assert_eq!(K3::PARENT_VALUE, Some("KN")); + assert_eq!(K3::K, Some(3)); +} + +#[test] +fn test_kvalue_k4() { + assert_eq!(K4::CATEGORY, "k"); + assert_eq!(K4::VALUE, "K4"); + assert_eq!(K4::PARENT_VALUE, Some("KN")); + assert_eq!(K4::K, Some(4)); +} + +#[test] +fn test_kvalue_k5() { + assert_eq!(K5::CATEGORY, "k"); + assert_eq!(K5::VALUE, "K5"); + assert_eq!(K5::PARENT_VALUE, Some("KN")); + assert_eq!(K5::K, Some(5)); +} + +#[test] +fn test_kvalue_kn() { + assert_eq!(KN::CATEGORY, "k"); + assert_eq!(KN::VALUE, "KN"); + assert_eq!(KN::PARENT_VALUE, None); + assert_eq!(KN::K, None); +} + +#[test] +fn test_kvalue_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "k") + .collect(); + // All specific K values have KN as parent (flat hierarchy, no chain) + for entry in &entries { + if entry.value == "KN" { + assert_eq!(entry.parent, None, "KN should have no parent"); + } else { + assert_eq!( + entry.parent, + Some("KN"), + "K value {} should have KN as parent", + entry.value + ); + } + } +} + +// --- Graph type VariantParam tests --- + +use crate::topology::HyperGraph; +use crate::topology::{Graph, SimpleGraph, UnitDiskGraph}; + +#[test] +fn test_simple_graph_variant_param() { + assert_eq!(SimpleGraph::CATEGORY, "graph"); + assert_eq!(SimpleGraph::VALUE, "SimpleGraph"); + assert_eq!(SimpleGraph::PARENT_VALUE, Some("HyperGraph")); +} + +#[test] +fn test_unit_disk_graph_variant_param() { + assert_eq!(UnitDiskGraph::CATEGORY, "graph"); + assert_eq!(UnitDiskGraph::VALUE, "UnitDiskGraph"); + assert_eq!(UnitDiskGraph::PARENT_VALUE, Some("SimpleGraph")); +} + +#[test] +fn test_hyper_graph_variant_param() { + assert_eq!(HyperGraph::CATEGORY, "graph"); + assert_eq!(HyperGraph::VALUE, "HyperGraph"); + assert_eq!(HyperGraph::PARENT_VALUE, None); +} + +#[test] +fn test_graph_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "graph") + .collect(); + assert!(entries + .iter() + .any(|e| e.value == "HyperGraph" && e.parent.is_none())); + assert!(entries + .iter() + .any(|e| e.value == "SimpleGraph" && e.parent == Some("HyperGraph"))); + assert!(entries + .iter() + .any(|e| e.value == "UnitDiskGraph" && e.parent == Some("SimpleGraph"))); +} + +#[test] +fn test_simple_graph_cast_to_parent() { + let sg = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); + let hg: HyperGraph = sg.cast_to_parent(); + assert_eq!(hg.num_vertices(), 3); + assert_eq!(hg.num_edges(), 2); +} + +#[test] +fn test_udg_cast_to_parent() { + let udg = UnitDiskGraph::new(vec![(0.0, 0.0), (0.5, 0.0), (2.0, 0.0)], 1.0); + let sg: SimpleGraph = udg.cast_to_parent(); + assert_eq!(sg.num_vertices(), 3); + // Only the first two points are within distance 1.0 + assert!(sg.has_edge(0, 1)); + assert!(!sg.has_edge(0, 2)); +} + +// --- Weight type VariantParam tests --- + +use crate::types::One; + +#[test] +fn test_weight_f64_variant_param() { + assert_eq!(::CATEGORY, "weight"); + assert_eq!(::VALUE, "f64"); + assert_eq!(::PARENT_VALUE, None); +} + +#[test] +fn test_weight_i32_variant_param() { + assert_eq!(::CATEGORY, "weight"); + assert_eq!(::VALUE, "i32"); + assert_eq!(::PARENT_VALUE, Some("f64")); +} + +#[test] +fn test_weight_one_variant_param() { + assert_eq!(One::CATEGORY, "weight"); + assert_eq!(One::VALUE, "One"); + assert_eq!(One::PARENT_VALUE, Some("i32")); +} + +#[test] +fn test_weight_cast_chain() { + let one = One; + let i: i32 = one.cast_to_parent(); + assert_eq!(i, 1); + let f: f64 = i.cast_to_parent(); + assert_eq!(f, 1.0); +} + +#[test] +fn test_weight_variant_entries() { + let entries: Vec<_> = inventory::iter::() + .filter(|e| e.category == "weight") + .collect(); + assert!(entries + .iter() + .any(|e| e.value == "f64" && e.parent.is_none())); + assert!(entries + .iter() + .any(|e| e.value == "i32" && e.parent == Some("f64"))); + assert!(entries + .iter() + .any(|e| e.value == "One" && e.parent == Some("i32"))); +} diff --git a/src/variant.rs b/src/variant.rs index 0481b4dd..fa8587c1 100644 --- a/src/variant.rs +++ b/src/variant.rs @@ -1,44 +1,181 @@ -//! Variant attribute utilities. +//! Variant system for type-level problem parameterization. +//! +//! Types declare their variant category, value, and parent via `VariantParam`. +//! The `impl_variant_param!` macro registers types with both the trait and +//! the runtime `VariantTypeEntry` inventory. The `variant_params!` macro +//! composes `Problem::variant()` bodies from type parameter names. -use std::any::type_name; +/// A type that participates in the variant system. +/// +/// Declares its category (e.g., `"graph"`), value (e.g., `"SimpleGraph"`), +/// and optional parent in the subtype hierarchy. +pub trait VariantParam: 'static { + /// Category name (e.g., `"graph"`, `"weight"`, `"k"`). + const CATEGORY: &'static str; + /// Type name within the category (e.g., `"SimpleGraph"`, `"i32"`). + const VALUE: &'static str; + /// Parent type name in the subtype hierarchy, or `None` for root types. + const PARENT_VALUE: Option<&'static str>; +} + +/// Types that can convert themselves to their parent in the variant hierarchy. +pub trait CastToParent: VariantParam { + /// The parent type. + type Parent: VariantParam; + /// Convert this value to its parent type. + fn cast_to_parent(&self) -> Self::Parent; +} -/// Convert const generic usize to static str (for common values). +/// K-value marker trait for types that represent a const-generic K parameter. /// -/// This is useful for including const generic parameters in problem variant IDs. -/// For values 1-10, returns the string representation. For other values, returns "N". +/// Types implementing this trait declare an optional K value. `None` means +/// the type represents an arbitrary K (like KN), while `Some(k)` means +/// a specific value (like K2, K3). +pub trait KValue: VariantParam + Clone + 'static { + /// The K value, or `None` for arbitrary K. + const K: Option; +} + +/// Runtime-discoverable variant type registration. /// -/// # Example +/// Built by `impl_variant_param!` macro, collected by `inventory`. +pub struct VariantTypeEntry { + /// Category name (e.g., `"graph"`, `"weight"`, `"k"`). + pub category: &'static str, + /// Type name within the category (e.g., `"SimpleGraph"`, `"i32"`). + pub value: &'static str, + /// Parent type name in the subtype hierarchy, or `None` for root types. + pub parent: Option<&'static str>, +} + +inventory::collect!(VariantTypeEntry); + +/// Implement `VariantParam` (and optionally `CastToParent` and/or `KValue`) for a type, +/// and register a `VariantTypeEntry` with inventory. /// -/// ``` -/// use problemreductions::variant::const_usize_str; +/// # Usage +/// +/// ```text +/// // Root type (no parent): +/// impl_variant_param!(SimpleGraph, "graph"); +/// +/// // Type with parent -- cast closure required: +/// impl_variant_param!(UnitDiskGraph, "graph", parent: SimpleGraph, +/// cast: |g| SimpleGraph::new(g.num_vertices(), g.edges())); +/// +/// // Root K type (no parent, with K value): +/// impl_variant_param!(KN, "k", k: None); /// -/// assert_eq!(const_usize_str::<3>(), "3"); -/// assert_eq!(const_usize_str::<10>(), "10"); -/// assert_eq!(const_usize_str::<100>(), "N"); +/// // K type with parent + cast + K value: +/// impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3)); /// ``` -pub const fn const_usize_str() -> &'static str { - match N { - 1 => "1", - 2 => "2", - 3 => "3", - 4 => "4", - 5 => "5", - 6 => "6", - 7 => "7", - 8 => "8", - 9 => "9", - 10 => "10", - _ => "N", - } +#[macro_export] +macro_rules! impl_variant_param { + // Root type (no parent, no cast) + ($ty:ty, $cat:expr) => { + impl $crate::variant::VariantParam for $ty { + const CATEGORY: &'static str = $cat; + const VALUE: &'static str = stringify!($ty); + const PARENT_VALUE: Option<&'static str> = None; + } + ::inventory::submit! { + $crate::variant::VariantTypeEntry { + category: $cat, + value: stringify!($ty), + parent: None, + } + } + }; + // Type with parent + cast closure + ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr) => { + impl $crate::variant::VariantParam for $ty { + const CATEGORY: &'static str = $cat; + const VALUE: &'static str = stringify!($ty); + const PARENT_VALUE: Option<&'static str> = Some(stringify!($parent)); + } + impl $crate::variant::CastToParent for $ty { + type Parent = $parent; + fn cast_to_parent(&self) -> $parent { + let f: fn(&$ty) -> $parent = $cast; + f(self) + } + } + ::inventory::submit! { + $crate::variant::VariantTypeEntry { + category: $cat, + value: stringify!($ty), + parent: Some(stringify!($parent)), + } + } + }; + // KValue root type (no parent, with k value) + ($ty:ty, $cat:expr, k: $k:expr) => { + $crate::impl_variant_param!($ty, $cat); + impl $crate::variant::KValue for $ty { + const K: Option = $k; + } + }; + // KValue type with parent + cast + k value + ($ty:ty, $cat:expr, parent: $parent:ty, cast: $cast:expr, k: $k:expr) => { + $crate::impl_variant_param!($ty, $cat, parent: $parent, cast: $cast); + impl $crate::variant::KValue for $ty { + const K: Option = $k; + } + }; } -/// Extract short type name from full path. -/// e.g., "problemreductions::graph_types::SimpleGraph" -> "SimpleGraph" -pub fn short_type_name() -> &'static str { - let full = type_name::(); - full.rsplit("::").next().unwrap_or(full) +/// Compose a `Problem::variant()` body from type parameter names. +/// +/// All variant dimensions must be types implementing `VariantParam`. +/// +/// # Usage +/// +/// ```text +/// variant_params![] // -> vec![] +/// variant_params![G, W] // -> vec![(G::CATEGORY, G::VALUE), ...] +/// ``` +#[macro_export] +macro_rules! variant_params { + () => { vec![] }; + ($($T:ident),+) => { + vec![$((<$T as $crate::variant::VariantParam>::CATEGORY, + <$T as $crate::variant::VariantParam>::VALUE)),+] + }; } +// --- Concrete KValue types --- + +/// K=1 (e.g., 1-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K1; + +/// K=2 (e.g., 2-SAT, 2-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K2; + +/// K=3 (e.g., 3-SAT, 3-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K3; + +/// K=4 (e.g., 4-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K4; + +/// K=5 (e.g., 5-coloring). +#[derive(Clone, Copy, Debug, Default)] +pub struct K5; + +/// Generic K (any value). Used for reductions that apply to all K. +#[derive(Clone, Copy, Debug, Default)] +pub struct KN; + +impl_variant_param!(KN, "k", k: None); +impl_variant_param!(K5, "k", parent: KN, cast: |_| KN, k: Some(5)); +impl_variant_param!(K4, "k", parent: KN, cast: |_| KN, k: Some(4)); +impl_variant_param!(K3, "k", parent: KN, cast: |_| KN, k: Some(3)); +impl_variant_param!(K2, "k", parent: KN, cast: |_| KN, k: Some(2)); +impl_variant_param!(K1, "k", parent: KN, cast: |_| KN, k: Some(1)); + #[cfg(test)] #[path = "unit_tests/variant.rs"] mod tests; diff --git a/tests/suites/integration.rs b/tests/suites/integration.rs index b7721809..a6c975c0 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>::new(3, vec![(0, 1), (1, 2)]); + let problem = KColoring::::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); diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index 766a4b5e..f93485e8 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -568,7 +568,7 @@ mod qubo_reductions { assert_eq!(data.source.num_colors, 3); - let kc = KColoring::<3, SimpleGraph>::new(data.source.num_vertices, data.source.edges); + let kc = KColoring::::new(data.source.num_vertices, data.source.edges); let reduction = ReduceTo::::reduce_to(&kc); let qubo = reduction.target_problem(); @@ -670,7 +670,7 @@ mod qubo_reductions { }) .collect(); - let ksat = KSatisfiability::<2>::new(data.source.num_variables, clauses); + let ksat = KSatisfiability::::new(data.source.num_variables, clauses); let reduction = ReduceTo::::reduce_to(&ksat); let qubo = reduction.target_problem();