Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"MaxCut": [Max-Cut],
"GraphPartitioning": [Graph Partitioning],
"HamiltonianPath": [Hamiltonian Path],
"ShortestWeightConstrainedPath": [Shortest Weight-Constrained Path],
"IsomorphicSpanningTree": [Isomorphic Spanning Tree],
"KColoring": [$k$-Coloring],
"MinimumDominatingSet": [Minimum Dominating Set],
Expand Down Expand Up @@ -631,6 +632,62 @@ Graph Partitioning is a core NP-hard problem arising in VLSI design, parallel co
]
]
}
#{
let x = load-model-example("ShortestWeightConstrainedPath")
let nv = graph-num-vertices(x.instance)
let edges = x.instance.graph.inner.edges.map(e => (e.at(0), e.at(1)))
let lengths = x.instance.edge_lengths
let weights = x.instance.edge_weights
let s = x.instance.source_vertex
let t = x.instance.target_vertex
let K = x.instance.length_bound
let W = x.instance.weight_bound
let sample = x.samples.at(0)
let path-config = sample.config
let path-edges = edges.enumerate().filter(((idx, _)) => path-config.at(idx) == 1).map(((idx, e)) => e)
let path-order = (0, 2, 3, 5)
let satisfying-count = x.optimal.len()
[
#problem-def("ShortestWeightConstrainedPath")[
Given an undirected graph $G = (V, E)$ with positive edge lengths $l: E -> ZZ^+$, positive edge weights $w: E -> ZZ^+$, designated vertices $s, t in V$, and bounds $K, W in ZZ^+$, determine whether there exists a simple path $P$ from $s$ to $t$ such that $sum_(e in P) l(e) <= K$ and $sum_(e in P) w(e) <= W$.
][
Also called the _restricted shortest path_ or _resource-constrained shortest path_ problem. Garey and Johnson list it as ND30 and show NP-completeness via transformation from Partition @garey1979. The model captures bicriteria routing: one resource measures path length or delay, while the other captures a second consumable budget such as cost, risk, or bandwidth. Because pseudo-polynomial dynamic programming formulations are known @joksch1966, the hardness is weak rather than strong; approximation schemes were later developed by Hassin @hassin1992 and improved by Lorenz and Raz @lorenzraz2001.

The implementation catalog reports the natural brute-force complexity of the edge-subset encoding used here: with $m = |E|$ binary variables, exhaustive search over all candidate subsets costs $O^*(2^m)$. A configuration is satisfying precisely when the selected edges form a single simple $s$-$t$ path and both resource sums stay within their bounds.

*Example.* Consider the graph on #nv vertices with source $s = v_#s$, target $t = v_#t$, length bound $K = #K$, and weight bound $W = #W$. Edge labels are written as $(l(e), w(e))$. The highlighted path $#path-order.map(v => $v_#v$).join($arrow$)$ uses edges ${#path-edges.map(((u, v)) => $(v_#u, v_#v)$).join(", ")}$, so its total length is $4 + 1 + 4 = 9 <= #K$ and its total weight is $1 + 3 + 3 = 7 <= #W$. This instance has #satisfying-count satisfying edge selections; another feasible path is $v_0 arrow v_1 arrow v_4 arrow v_5$.

#figure({
let blue = graph-colors.at(0)
let gray = luma(200)
let verts = ((0, 1), (1.5, 1.8), (1.5, 0.2), (3, 1.8), (3, 0.2), (4.5, 1))
canvas(length: 1cm, {
import draw: *
for (idx, (u, v)) in edges.enumerate() {
let on-path = path-config.at(idx) == 1
g-edge(verts.at(u), verts.at(v), stroke: if on-path { 2pt + blue } else { 1pt + gray })
let mx = (verts.at(u).at(0) + verts.at(v).at(0)) / 2
let my = (verts.at(u).at(1) + verts.at(v).at(1)) / 2
let dx = if idx == 7 { -0.25 } else if idx == 5 or idx == 6 { 0.15 } else { 0 }
let dy = if idx == 0 or idx == 2 or idx == 5 { 0.16 } else if idx == 1 or idx == 4 or idx == 6 { -0.16 } else if idx == 7 { 0.12 } else { 0 }
draw.content(
(mx + dx, my + dy),
text(7pt, fill: luma(80))[#("(" + str(int(lengths.at(idx))) + ", " + str(int(weights.at(idx))) + ")")]
)
}
for (k, pos) in verts.enumerate() {
let on-path = path-order.any(v => v == k)
g-node(pos, name: "v" + str(k),
fill: if on-path { blue } else { white },
label: if on-path { text(fill: white)[$v_#k$] } else { [$v_#k$] })
}
})
},
caption: [Shortest Weight-Constrained Path instance with edge labels $(l(e), w(e))$. The highlighted path $v_0 arrow v_2 arrow v_3 arrow v_5$ satisfies both bounds.],
) <fig:shortest-weight-constrained-path>
]
]
}
#{
let x = load-model-example("KColoring")
let nv = graph-num-vertices(x.instance)
Expand Down
33 changes: 33 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,39 @@ @book{garey1979
year = {1979}
}

@article{joksch1966,
author = {Hans C. Joksch},
title = {The Shortest Route Problem with Constraints},
journal = {Journal of Mathematical Analysis and Applications},
volume = {14},
number = {2},
pages = {191--197},
year = {1966},
doi = {10.1016/0022-247X(66)90002-6}
}

@article{hassin1992,
author = {Refael Hassin},
title = {Approximation Schemes for the Restricted Shortest Path Problem},
journal = {Mathematics of Operations Research},
volume = {17},
number = {1},
pages = {36--42},
year = {1992},
doi = {10.1287/moor.17.1.36}
}

@article{lorenzraz2001,
author = {Daniel H. Lorenz and Danny Raz},
title = {A Simple Efficient Approximation Scheme for the Restricted Shortest Path Problem},
journal = {Operations Research Letters},
volume = {28},
number = {5},
pages = {213--219},
year = {2001},
doi = {10.1016/S0167-6377(01)00079-6}
}

@article{gareyJohnsonStockmeyer1976,
author = {Michael R. Garey and David S. Johnson and Larry Stockmeyer},
title = {Some Simplified {NP}-Complete Graph Problems},
Expand Down
1 change: 1 addition & 0 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ pred create QUBO --matrix "1,0.5;0.5,2" -o qubo.json
pred create KColoring --k 3 --graph 0-1,1-2,2-0 -o kcol.json
pred create SpinGlass --graph 0-1,1-2 -o sg.json
pred create MaxCut --graph 0-1,1-2,2-0 -o maxcut.json
pred create ShortestWeightConstrainedPath --graph 0-1,0-2,1-3,2-3,2-4,3-5,4-5,1-4 --edge-lengths 2,4,3,1,5,4,2,6 --edge-weights 5,1,2,3,2,3,1,1 --source-vertex 0 --target-vertex 5 --length-bound 10 --weight-bound 8 -o swcp.json
pred create Factoring --target 15 --bits-m 4 --bits-n 4 -o factoring.json
pred create Factoring --target 21 --bits-m 3 --bits-n 3 -o factoring2.json
pred create X3C --universe 9 --sets "0,1,2;0,2,4;3,4,5;3,5,7;6,7,8;1,4,6;2,5,8" -o x3c.json
Expand Down
41 changes: 41 additions & 0 deletions docs/src/reductions/problem_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,47 @@
}
]
},
{
"name": "ShortestWeightConstrainedPath",
"description": "Find a simple s-t path whose total length and weight stay within given bounds",
"fields": [
{
"name": "graph",
"type_name": "G",
"description": "The underlying graph G=(V,E)"
},
{
"name": "edge_lengths",
"type_name": "Vec<W>",
"description": "Edge lengths l: E -> ZZ_(> 0)"
},
{
"name": "edge_weights",
"type_name": "Vec<W>",
"description": "Edge weights w: E -> ZZ_(> 0)"
},
{
"name": "source_vertex",
"type_name": "usize",
"description": "Source vertex s"
},
{
"name": "target_vertex",
"type_name": "usize",
"description": "Target vertex t"
},
{
"name": "length_bound",
"type_name": "W::Sum",
"description": "Upper bound K on total path length"
},
{
"name": "weight_bound",
"type_name": "W::Sum",
"description": "Upper bound W on total path weight"
}
]
},
{
"name": "SpinGlass",
"description": "Minimize Ising Hamiltonian on a graph",
Expand Down
30 changes: 20 additions & 10 deletions docs/src/reductions/reduction_graph.json
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,16 @@
"doc_path": "models/misc/struct.ShortestCommonSupersequence.html",
"complexity": "alphabet_size ^ bound"
},
{
"name": "ShortestWeightConstrainedPath",
"variant": {
"graph": "SimpleGraph",
"weight": "i32"
},
"category": "graph",
"doc_path": "models/graph/struct.ShortestWeightConstrainedPath.html",
"complexity": "2^num_edges"
},
{
"name": "SpinGlass",
"variant": {
Expand Down Expand Up @@ -549,7 +559,7 @@
},
{
"source": 4,
"target": 54,
"target": 55,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -713,7 +723,7 @@
},
{
"source": 21,
"target": 56,
"target": 57,
"overhead": [
{
"field": "num_elements",
Expand Down Expand Up @@ -769,7 +779,7 @@
},
{
"source": 25,
"target": 54,
"target": 55,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -1215,7 +1225,7 @@
},
{
"source": 49,
"target": 53,
"target": 54,
"overhead": [
{
"field": "num_spins",
Expand Down Expand Up @@ -1300,7 +1310,7 @@
"doc_path": "rules/sat_minimumdominatingset/index.html"
},
{
"source": 53,
"source": 54,
"target": 49,
"overhead": [
{
Expand All @@ -1311,7 +1321,7 @@
"doc_path": "rules/spinglass_qubo/index.html"
},
{
"source": 54,
"source": 55,
"target": 25,
"overhead": [
{
Expand All @@ -1326,8 +1336,8 @@
"doc_path": "rules/spinglass_maxcut/index.html"
},
{
"source": 54,
"target": 53,
"source": 55,
"target": 54,
"overhead": [
{
"field": "num_spins",
Expand All @@ -1341,7 +1351,7 @@
"doc_path": "rules/spinglass_casts/index.html"
},
{
"source": 57,
"source": 58,
"target": 12,
"overhead": [
{
Expand All @@ -1356,7 +1366,7 @@
"doc_path": "rules/travelingsalesman_ilp/index.html"
},
{
"source": 57,
"source": 58,
"target": 49,
"overhead": [
{
Expand Down
16 changes: 16 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ TIP: Run `pred create <PROBLEM>` (no other flags) to see problem-specific help.
Flags by problem type:
MIS, MVC, MaxClique, MinDomSet --graph, --weights
MaxCut, MaxMatching, TSP --graph, --edge-weights
ShortestWeightConstrainedPath --graph, --edge-lengths, --edge-weights, --source-vertex, --target-vertex, --length-bound, --weight-bound
MaximalIS --graph, --weights
SAT, KSAT --num-vars, --clauses [--k]
QUBO --matrix
Expand Down Expand Up @@ -286,6 +287,9 @@ pub struct CreateArgs {
/// Edge weights (e.g., 2,3,1) [default: all 1s]
#[arg(long)]
pub edge_weights: Option<String>,
/// Edge lengths (e.g., 2,3,1) [default: all 1s]
#[arg(long)]
pub edge_lengths: Option<String>,
/// Pairwise couplings J_ij for SpinGlass (e.g., 1,-1,1) [default: all 1s]
#[arg(long)]
pub couplings: Option<String>,
Expand All @@ -310,6 +314,12 @@ pub struct CreateArgs {
/// Number of vertices for random graph generation
#[arg(long)]
pub num_vertices: Option<usize>,
/// Source vertex for path problems
#[arg(long)]
pub source_vertex: Option<usize>,
/// Target vertex for path problems
#[arg(long)]
pub target_vertex: Option<usize>,
/// Edge probability for random graph generation (0.0 to 1.0) [default: 0.5]
#[arg(long)]
pub edge_prob: Option<f64>,
Expand Down Expand Up @@ -376,6 +386,12 @@ pub struct CreateArgs {
/// Upper bound (for RuralPostman or SCS)
#[arg(long)]
pub bound: Option<i64>,
/// Upper bound on total path length
#[arg(long)]
pub length_bound: Option<i32>,
/// Upper bound on total path weight
#[arg(long)]
pub weight_bound: Option<i32>,
/// Pattern graph edge list for SubgraphIsomorphism (e.g., 0-1,1-2,2-0)
#[arg(long)]
pub pattern: Option<String>,
Expand Down
Loading
Loading