Skip to content

Commit 60ec662

Browse files
authored
Fix batch of issues found in post-merge review (#642)
* save * update add model skill to include docs unit tests * add tests * new tests and fix missing examples * feat(subsetsum): support BigUint instances
1 parent e73796d commit 60ec662

46 files changed

Lines changed: 1311 additions & 197 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/add-model/SKILL.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ Required tests:
149149
- `test_<name>_direction` -- verify optimization direction (if optimization problem)
150150
- `test_<name>_serialization` -- round-trip serde test (optional but recommended)
151151
- `test_<name>_solver` -- verify brute-force solver finds correct solutions
152+
- `test_<name>_paper_example` -- **use the same instance from the paper example** (Step 6), verify the claimed solution is valid/optimal and the solution count matches
153+
154+
The `test_<name>_paper_example` test is critical for consistency between code and paper. It must:
155+
1. Construct the exact same instance shown in the paper's example figure
156+
2. Evaluate the solution shown in the paper and assert it is valid (and optimal for optimization problems)
157+
3. Use `BruteForce` to find all optimal/satisfying solutions and assert the count matches the paper's claim
158+
159+
This test should be written **after** Step 6 (paper entry), once the example instance and solution are finalized. If writing tests before the paper, use the same instance you plan to use in the paper and come back to verify consistency.
152160

153161
Link the test file via `#[cfg(test)] #[path = "..."] mod tests;` at the bottom of the model file.
154162

@@ -233,3 +241,4 @@ If running standalone (not inside `make run-plan`), invoke [review-implementatio
233241
| Missing from CLI help table | Must add entry to "Flags by problem type" table in `cli.rs` `after_help` |
234242
| Schema lists derived fields | Schema should list constructor params, not internal fields (e.g., `matrix, k` not `matrix, m, n, k`) |
235243
| Forgetting trait_consistency | Must add entry in `test_all_problems_implement_trait_correctly` (and `test_direction` for optimization) in `src/unit_tests/trait_consistency.rs` |
244+
| Paper example not tested | Must include `test_<name>_paper_example` that verifies the exact instance, solution, and solution count shown in the paper |

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ bitvec = "1.0"
2828
serde = { version = "1.0", features = ["derive"] }
2929
serde_json = "1.0"
3030
thiserror = "2.0"
31+
num-bigint = "0.4"
3132
num-traits = "0.2"
3233
good_lp = { version = "1.8", default-features = false, optional = true }
3334
inventory = "0.3"

docs/paper/examples/maximumindependentset_to_maximumclique.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"target": {
3232
"problem": "MaximumClique",
3333
"variant": {
34-
"weight": "i32",
35-
"graph": "SimpleGraph"
34+
"graph": "SimpleGraph",
35+
"weight": "i32"
3636
},
3737
"instance": {
3838
"edges": [

docs/paper/reductions.typ

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"ShortestCommonSupersequence": [Shortest Common Supersequence],
6565
"MinimumSumMulticenter": [Minimum Sum Multicenter],
6666
"SubgraphIsomorphism": [Subgraph Isomorphism],
67+
"PartitionIntoTriangles": [Partition Into Triangles],
6768
"FlowShopScheduling": [Flow Shop Scheduling],
6869
)
6970

@@ -451,6 +452,32 @@ Graph Partitioning is a core NP-hard problem arising in VLSI design, parallel co
451452
The best known exact algorithm is Björklund's randomized $O^*(1.657^n)$ "Determinant Sums" method @bjorklund2014, which applies to both Hamiltonian path and circuit. The classical Held--Karp dynamic programming algorithm solves it in $O(n^2 dot 2^n)$ deterministic time.
452453

453454
Variables: $n = |V|$ values forming a permutation. Position $i$ holds the vertex visited at step $i$. A configuration is satisfying when it forms a valid permutation of all vertices and consecutive vertices are adjacent in $G$.
455+
456+
*Example.* Consider the graph $G$ on 6 vertices with edges ${(0,1), (0,2), (1,3), (2,3), (3,4), (3,5), (2,4), (1,5)}$. The sequence $[0, 2, 4, 3, 1, 5]$ is a Hamiltonian path: it visits every vertex exactly once, and each consecutive pair is adjacent — $(0,2), (2,4), (4,3), (3,1), (1,5) in E$.
457+
458+
#figure({
459+
let blue = graph-colors.at(0)
460+
let gray = luma(200)
461+
canvas(length: 1cm, {
462+
import draw: *
463+
// 6 vertices in two rows
464+
let verts = ((0, 1.5), (1.5, 1.5), (3, 1.5), (1.5, 0), (3, 0), (0, 0))
465+
let edges = ((0,1),(0,2),(1,3),(2,3),(3,4),(3,5),(2,4),(1,5))
466+
// Hamiltonian path edges: 0-2, 2-4, 4-3, 3-1, 1-5
467+
let path-edges = ((0,2),(2,4),(4,3),(3,1),(1,5))
468+
for (u, v) in edges {
469+
let on-path = path-edges.any(e => (e.at(0) == u and e.at(1) == v) or (e.at(0) == v and e.at(1) == u))
470+
g-edge(verts.at(u), verts.at(v), stroke: if on-path { 2pt + blue } else { 1pt + gray })
471+
}
472+
for (k, pos) in verts.enumerate() {
473+
g-node(pos, name: "v" + str(k),
474+
fill: blue,
475+
label: text(fill: white)[$v_#k$])
476+
}
477+
})
478+
},
479+
caption: [Hamiltonian Path in a 6-vertex graph. Blue edges show the path $v_0 arrow v_2 arrow v_4 arrow v_3 arrow v_1 arrow v_5$.],
480+
) <fig:hamiltonian-path>
454481
]
455482
#problem-def("IsomorphicSpanningTree")[
456483
Given a graph $G = (V, E)$ and a tree $T = (V_T, E_T)$ with $|V| = |V_T|$, determine whether $G$ contains a spanning tree isomorphic to $T$: does there exist a bijection $pi: V_T -> V$ such that for every edge ${u, v} in E_T$, ${pi(u), pi(v)} in E$?
@@ -655,6 +682,31 @@ Also known as the _p-median problem_. This is a classical NP-complete facility l
655682
The best known exact algorithm runs in $O^*(2^n)$ time by brute-force enumeration of all $binom(n, K)$ vertex subsets. Constant-factor approximation algorithms exist: Charikar et al. (1999) gave the first constant-factor result, and the best known ratio is $(2 + epsilon)$ by Cohen-Addad et al. (STOC 2022).
656683

657684
Variables: $n = |V|$ binary variables, one per vertex. $x_v = 1$ if vertex $v$ is selected as a center. A configuration is valid when exactly $K$ centers are selected and all vertices are reachable from at least one center.
685+
686+
*Example.* Consider the graph $G$ on 7 vertices with unit weights $w(v) = 1$ and unit edge lengths, edges ${(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (0,6), (2,5)}$, and $K = 2$. Placing centers at $P = {v_2, v_5}$ gives distances $d(v_0) = 2$, $d(v_1) = 1$, $d(v_2) = 0$, $d(v_3) = 1$, $d(v_4) = 1$, $d(v_5) = 0$, $d(v_6) = 1$, for a total cost of $2 + 1 + 0 + 1 + 1 + 0 + 1 = 6$. This is optimal.
687+
688+
#figure({
689+
let blue = graph-colors.at(0)
690+
let gray = luma(200)
691+
canvas(length: 1cm, {
692+
import draw: *
693+
// 7 vertices on a rough circle
694+
let verts = ((-1.5, 0.8), (0, 1.5), (1.5, 0.8), (1.5, -0.8), (0, -1.5), (-1.5, -0.8), (-2.2, 0))
695+
let edges = ((0,1),(1,2),(2,3),(3,4),(4,5),(5,6),(0,6),(2,5))
696+
for (u, v) in edges {
697+
g-edge(verts.at(u), verts.at(v), stroke: 1pt + gray)
698+
}
699+
let centers = (2, 5)
700+
for (k, pos) in verts.enumerate() {
701+
let is-center = centers.any(c => c == k)
702+
g-node(pos, name: "v" + str(k),
703+
fill: if is-center { blue } else { white },
704+
label: if is-center { text(fill: white)[$v_#k$] } else { [$v_#k$] })
705+
}
706+
})
707+
},
708+
caption: [Minimum Sum Multicenter with $K = 2$ on a 7-vertex graph. Centers $v_2$ and $v_5$ (blue) achieve optimal total weighted distance 6.],
709+
) <fig:minimum-sum-multicenter>
658710
]
659711

660712
== Set Problems
@@ -1019,6 +1071,37 @@ Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipa
10191071
) <fig:biclique-cover>
10201072
]
10211073

1074+
#problem-def("PartitionIntoTriangles")[
1075+
Given a graph $G = (V, E)$ with $|V| = 3q$ for some integer $q$, determine whether the vertices of $G$ can be partitioned into $q$ disjoint triples $V_1, dots, V_q$, each containing exactly 3 vertices, such that for each $V_i = {u_i, v_i, w_i}$, all three edges ${u_i, v_i}$, ${u_i, w_i}$, and ${v_i, w_i}$ belong to $E$.
1076+
][
1077+
Partition Into Triangles is NP-complete by transformation from 3-Dimensional Matching @garey1979[GT11]. It remains NP-complete on graphs of maximum degree 4, with an exact algorithm running in $O^*(1.0222^n)$ for bounded-degree-4 graphs @vanrooij2013. The general brute-force bound is $O^*(2^n)$#footnote[No algorithm improving on brute-force enumeration is known for general Partition Into Triangles.].
1078+
1079+
*Example.* Consider $G$ with $n = 6$ vertices ($q = 2$) and edges ${0,1}$, ${0,2}$, ${1,2}$, ${3,4}$, ${3,5}$, ${4,5}$, ${0,3}$. The partition $V_1 = {v_0, v_1, v_2}$, $V_2 = {v_3, v_4, v_5}$ is valid: $V_1$ forms a triangle (edges ${0,1}$, ${0,2}$, ${1,2}$ all present) and $V_2$ forms a triangle (edges ${3,4}$, ${3,5}$, ${4,5}$ all present). The cross-edge ${0,3}$ is unused. Swapping $v_2$ and $v_3$ yields $V'_1 = {v_0, v_1, v_3}$, which fails because ${1, 3} in.not E$.
1080+
1081+
#figure(
1082+
canvas(length: 1cm, {
1083+
import draw: *
1084+
// Two triangles side by side with a cross-edge
1085+
let verts = ((0, 1.2), (1, 0), (-1, 0), (3, 1.2), (4, 0), (2, 0))
1086+
let edges = ((0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5), (0, 3))
1087+
let tri1 = (0, 1, 2)
1088+
let tri2 = (3, 4, 5)
1089+
// Draw edges
1090+
for (u, v) in edges {
1091+
let is-cross = u == 0 and v == 3
1092+
g-edge(verts.at(u), verts.at(v),
1093+
stroke: if is-cross { 1pt + luma(180) } else if tri1.contains(u) and tri1.contains(v) { 1.5pt + graph-colors.at(0) } else { 1.5pt + rgb("#76b7b2") })
1094+
}
1095+
// Draw vertices
1096+
for (k, p) in verts.enumerate() {
1097+
let c = if tri1.contains(k) { graph-colors.at(0).lighten(70%) } else { rgb("#76b7b2").lighten(70%) }
1098+
g-node(p, name: "v" + str(k), fill: c, label: $v_#k$)
1099+
}
1100+
}),
1101+
caption: [Partition Into Triangles: $V_1 = {v_0, v_1, v_2}$ (blue) and $V_2 = {v_3, v_4, v_5}$ (teal) each form a triangle. The cross-edge $(v_0, v_3)$ (gray) is unused.],
1102+
) <fig:partition-triangles>
1103+
]
1104+
10221105
#problem-def("BinPacking")[
10231106
Given $n$ items with sizes $s_1, dots, s_n in RR^+$ and bin capacity $C > 0$, find an assignment $x: {1, dots, n} -> NN$ minimizing $|{x(i) : i = 1, dots, n}|$ (the number of distinct bins used) subject to $forall j: sum_(i: x(i) = j) s_i lt.eq C$.
10241107
][
@@ -1965,6 +2048,39 @@ The following reductions to Integer Linear Programming are straightforward formu
19652048
_Solution extraction._ For each position $k$, find vertex $v$ with $x_(v,k) = 1$ to recover the tour permutation; then select edges between consecutive positions.
19662049
]
19672050

2051+
#let tsp_qubo = load-example("travelingsalesman_to_qubo")
2052+
#let tsp_qubo_r = load-results("travelingsalesman_to_qubo")
2053+
#let tsp_qubo_sol = tsp_qubo_r.solutions.at(0)
2054+
2055+
#reduction-rule("TravelingSalesman", "QUBO",
2056+
example: true,
2057+
example-caption: [TSP on $K_3$ with weights $w_(01) = 1$, $w_(02) = 2$, $w_(12) = 3$: the QUBO ground state encodes the optimal tour with cost $1 + 2 + 3 = 6$.],
2058+
extra: [
2059+
*Step 1 -- Encode each tour position as a binary variable.* A tour is a permutation of $n$ vertices. Introduce $n^2 = #tsp_qubo.target.instance.num_vars$ binary variables $x_(v,p)$: vertex $v$ is at position $p$.
2060+
$ underbrace(x_(0,0) x_(0,1) x_(0,2), "vertex 0") #h(4pt) underbrace(x_(1,0) x_(1,1) x_(1,2), "vertex 1") #h(4pt) underbrace(x_(2,0) x_(2,1) x_(2,2), "vertex 2") $
2061+
2062+
*Step 2 -- Penalize invalid permutations.* The penalty $A = 1 + |w_(01)| + |w_(02)| + |w_(12)| = 1 + 1 + 2 + 3 = 7$ ensures any row/column constraint violation outweighs any tour cost. Row constraints (each vertex at exactly one position) and column constraints (each position has one vertex) contribute diagonal $-7$ and off-diagonal $+14$ within each group.\
2063+
2064+
*Step 3 -- Encode edge costs.* For each edge $(u,v)$ and position $p$, the products $x_(u,p) x_(v,(p+1) mod 3)$ and $x_(v,p) x_(u,(p+1) mod 3)$ add the edge weight $w_(u v)$ when vertices $u,v$ are consecutive in the tour. Since $K_3$ is complete, all pairs are edges with their actual weights.\
2065+
2066+
*Step 4 -- Verify a solution.* The QUBO ground state $bold(x) = (#tsp_qubo_sol.target_config.map(str).join(", "))$ encodes a valid tour. Reading the permutation: each 3-bit group has exactly one 1 (valid permutation #sym.checkmark). The tour cost equals $w_(01) + w_(02) + w_(12) = 1 + 2 + 3 = 6$.\
2067+
2068+
*Count:* #tsp_qubo_r.solutions.len() optimal QUBO solutions $= 3! = 6$. On $K_3$ with distinct edge weights $1, 2, 3$, every Hamiltonian cycle has cost $1 + 2 + 3 = 6$ (all edges used), and 3 cyclic tours $times$ 2 directions yield $6$ permutation matrices.
2069+
],
2070+
)[
2071+
Position-based QUBO encoding @lucas2014 maps a Hamiltonian tour to $n^2$ binary variables $x_(v,p)$, where $x_(v,p) = 1$ iff city $v$ is visited at position $p$. The QUBO Hamiltonian $H = H_A + H_B + H_C$ combines permutation constraints with the distance objective ($n^2$ variables indexed by $v dot n + p$).
2072+
][
2073+
_Construction._ For graph $G = (V, E)$ with $n = |V|$ and edge weights $w_(u v)$. Let $A = 1 + sum_((u,v) in E) |w_(u v)|$ be the penalty coefficient.
2074+
2075+
_Variables:_ Binary $x_(v,p) in {0, 1}$ for vertex $v in V$ and position $p in {0, dots, n-1}$. QUBO variable index: $v dot n + p$.
2076+
2077+
_QUBO matrix:_ (1) Row constraint $H_A = A sum_v (1 - sum_p x_(v,p))^2$: diagonal $Q[v n + p, v n + p] += -A$, off-diagonal $Q[v n + p, v n + p'] += 2A$ for $p < p'$. (2) Column constraint $H_B = A sum_p (1 - sum_v x_(v,p))^2$: symmetric to $H_A$. (3) Distance $H_C = sum_((u,v) in E) w_(u v) sum_p (x_(u,p) x_(v,(p+1) mod n) + x_(v,p) x_(u,(p+1) mod n))$. For non-edges, penalty $A$ replaces $w_(u v)$.
2078+
2079+
_Correctness._ ($arrow.r.double$) A valid tour defines a permutation matrix satisfying $H_A = H_B = 0$; the $H_C$ terms sum to the tour cost. ($arrow.l.double$) The minimum-energy state has $H_A = H_B = 0$ (penalty $A$ exceeds any tour cost), so it encodes a valid permutation; $H_C$ equals the tour cost, selecting the shortest tour.
2080+
2081+
_Solution extraction._ From QUBO solution $x^*$, for each position $p$ find the unique vertex $v$ with $x^*_(v n + p) = 1$. Map consecutive position pairs to edge indices.
2082+
]
2083+
19682084
#reduction-rule("LongestCommonSubsequence", "ILP")[
19692085
The match-pair ILP formulation @blum2021 encodes subsequence alignment as a binary optimization. For two strings $s_1$ (length $n_1$) and $s_2$ (length $n_2$), each position pair $(j_1, j_2)$ where $s_1[j_1] = s_2[j_2]$ yields a binary variable. Constraints enforce one-to-one matching and order preservation (no crossings). The objective maximizes the number of matched pairs.
19702086
][

docs/paper/references.bib

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
1+
@article{juttner2018,
2+
author = {Alpár Jüttner and Péter Madarasi},
3+
title = {VF2++ — An improved subgraph isomorphism algorithm},
4+
journal = {Discrete Applied Mathematics},
5+
volume = {242},
6+
pages = {69--81},
7+
year = {2018},
8+
doi = {10.1016/j.dam.2018.02.018}
9+
}
10+
11+
@article{johnson1954,
12+
author = {Selmer M. Johnson},
13+
title = {Optimal two- and three-stage production schedules with setup times included},
14+
journal = {Naval Research Logistics Quarterly},
15+
volume = {1},
16+
number = {1},
17+
pages = {61--68},
18+
year = {1954},
19+
doi = {10.1002/nav.3800010110}
20+
}
21+
22+
@article{shang2018,
23+
author = {Lei Shang and Chao Wan and Jianan Wang},
24+
title = {An exact algorithm for the three-machine flow shop problem},
25+
journal = {Computers \& Operations Research},
26+
volume = {91},
27+
pages = {79--89},
28+
year = {2018},
29+
doi = {10.1016/j.cor.2017.10.015}
30+
}
31+
132
@inproceedings{karp1972,
233
author = {Richard M. Karp},
334
title = {Reducibility among Combinatorial Problems},
@@ -118,6 +149,17 @@ @article{robson2001
118149
note = {Technical Report 1251-01, LaBRI, Université Bordeaux I}
119150
}
120151

152+
@article{vanrooij2013,
153+
author = {Johan M. M. van Rooij and Marcel van Kooten Niekerk and Hans L. Bodlaender},
154+
title = {Partition Into Triangles on Bounded Degree Graphs},
155+
journal = {Theory of Computing Systems},
156+
volume = {52},
157+
number = {4},
158+
pages = {687--718},
159+
year = {2013},
160+
doi = {10.1007/s00224-012-9412-5}
161+
}
162+
121163
@article{vanrooij2011,
122164
author = {Johan M. M. van Rooij and Hans L. Bodlaender},
123165
title = {Exact algorithms for dominating set},
@@ -543,6 +585,50 @@ @article{lucchesi1978
543585
doi = {10.1112/jlms/s2-17.3.369}
544586
}
545587

588+
@article{lenstra1976,
589+
author = {Jan Karel Lenstra and Alexander H. G. Rinnooy Kan},
590+
title = {On General Routing Problems},
591+
journal = {Networks},
592+
volume = {6},
593+
number = {3},
594+
pages = {273--280},
595+
year = {1976},
596+
doi = {10.1002/net.3230060305}
597+
}
598+
599+
@article{frederickson1979,
600+
author = {Greg N. Frederickson},
601+
title = {Approximation Algorithms for Some Postman Problems},
602+
journal = {Journal of the ACM},
603+
volume = {26},
604+
number = {3},
605+
pages = {538--554},
606+
year = {1979},
607+
doi = {10.1145/322139.322150}
608+
}
609+
610+
@article{alon1995,
611+
author = {Noga Alon and Raphael Yuster and Uri Zwick},
612+
title = {Color-coding},
613+
journal = {Journal of the ACM},
614+
volume = {42},
615+
number = {4},
616+
pages = {844--856},
617+
year = {1995},
618+
doi = {10.1145/210332.210337}
619+
}
620+
621+
@inproceedings{cordella2004,
622+
author = {Luigi P. Cordella and Pasquale Foggia and Carlo Sansone and Mario Vento},
623+
title = {A (Sub)Graph Isomorphism Algorithm for Matching Large Graphs},
624+
booktitle = {IEEE Transactions on Pattern Analysis and Machine Intelligence},
625+
volume = {26},
626+
number = {10},
627+
pages = {1367--1372},
628+
year = {2004},
629+
doi = {10.1109/TPAMI.2004.75}
630+
}
631+
546632
@article{papadimitriou1982,
547633
author = {Christos H. Papadimitriou and Mihalis Yannakakis},
548634
title = {The Complexity of Restricted Spanning Tree Problems},

docs/src/reductions/problem_schemas.json

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,27 @@
110110
}
111111
]
112112
},
113+
{
114+
"name": "FlowShopScheduling",
115+
"description": "Determine if a flow-shop schedule for jobs on m processors meets a deadline",
116+
"fields": [
117+
{
118+
"name": "num_processors",
119+
"type_name": "usize",
120+
"description": "Number of machines m"
121+
},
122+
{
123+
"name": "task_lengths",
124+
"type_name": "Vec<Vec<u64>>",
125+
"description": "task_lengths[j][i] = length of job j's task on machine i"
126+
},
127+
{
128+
"name": "deadline",
129+
"type_name": "u64",
130+
"description": "Global deadline D"
131+
}
132+
]
133+
},
113134
{
114135
"name": "GraphPartitioning",
115136
"description": "Find minimum cut balanced bisection of a graph",
@@ -345,6 +366,22 @@
345366
}
346367
]
347368
},
369+
{
370+
"name": "MinimumFeedbackArcSet",
371+
"description": "Find minimum weight feedback arc set in a directed graph",
372+
"fields": [
373+
{
374+
"name": "graph",
375+
"type_name": "DirectedGraph",
376+
"description": "The directed graph G=(V,A)"
377+
},
378+
{
379+
"name": "weights",
380+
"type_name": "Vec<W>",
381+
"description": "Arc weights w: A -> R"
382+
}
383+
]
384+
},
348385
{
349386
"name": "MinimumFeedbackVertexSet",
350387
"description": "Find minimum weight feedback vertex set in a directed graph",
@@ -580,16 +617,16 @@
580617
},
581618
{
582619
"name": "SubsetSum",
583-
"description": "Find a subset of integers that sums to exactly a target value",
620+
"description": "Find a subset of positive integers that sums to exactly a target value",
584621
"fields": [
585622
{
586623
"name": "sizes",
587-
"type_name": "Vec<i64>",
588-
"description": "Integer sizes s(a) for each element"
624+
"type_name": "Vec<BigUint>",
625+
"description": "Positive integer sizes s(a) for each element"
589626
},
590627
{
591628
"name": "target",
592-
"type_name": "i64",
629+
"type_name": "BigUint",
593630
"description": "Target sum B"
594631
}
595632
]

0 commit comments

Comments
 (0)