Skip to content
Merged
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
9 changes: 9 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"BicliqueCover": [Biclique Cover],
"BinPacking": [Bin Packing],
"ClosestVectorProblem": [Closest Vector Problem],
"RuralPostman": [Rural Postman],
"LongestCommonSubsequence": [Longest Common Subsequence],
"SubsetSum": [Subset Sum],
"MinimumFeedbackVertexSet": [Minimum Feedback Vertex Set],
Expand Down Expand Up @@ -981,6 +982,14 @@ Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipa
*Example.* Let $n = 4$ items with weights $(2, 3, 4, 5)$, values $(3, 4, 5, 7)$, and capacity $C = 7$. Selecting $S = {1, 2}$ (items with weights 3 and 4) gives total weight $3 + 4 = 7 lt.eq C$ and total value $4 + 5 = 9$. Selecting $S = {0, 3}$ (weights 2 and 5) gives weight $2 + 5 = 7 lt.eq C$ and value $3 + 7 = 10$, which is optimal.
]

#problem-def("RuralPostman")[
Given an undirected graph $G = (V, E)$ with edge lengths $l: E -> ZZ_(gt.eq 0)$, a subset $E' subset.eq E$ of required edges, and a bound $B in ZZ^+$, determine whether there exists a circuit (closed walk) in $G$ that traverses every edge in $E'$ and has total length at most $B$.
][
The Rural Postman Problem (RPP) is a fundamental NP-complete arc-routing problem @lenstra1976 that generalizes the Chinese Postman Problem. When $E' = E$, the problem reduces to finding an Eulerian circuit with minimum augmentation (polynomial-time solvable via $T$-join matching). For general $E' subset.eq E$, exact algorithms use dynamic programming over subsets of required edges in $O(n^2 dot 2^r)$ time, where $r = |E'|$ and $n = |V|$, analogous to the Held-Karp algorithm for TSP. The problem admits a $3 slash 2$-approximation for metric instances @frederickson1979.

*Example.* Consider a hexagonal graph with 6 vertices and 8 edges, where all outer edges have length 1 and two diagonal edges have length 2. The required edges are $E' = {(v_0, v_1), (v_2, v_3), (v_4, v_5)}$ with bound $B = 6$. The outer cycle $v_0 -> v_1 -> v_2 -> v_3 -> v_4 -> v_5 -> v_0$ covers all three required edges with total length $6 times 1 = 6 = B$, so the answer is YES.
]

#problem-def("LongestCommonSubsequence")[
Given $k$ strings $s_1, dots, s_k$ over a finite alphabet $Sigma$, find a longest string $w$ that is a subsequence of every $s_i$. A string $w$ is a _subsequence_ of $s$ if $w$ can be obtained by deleting zero or more characters from $s$ without changing the order of the remaining characters.
][
Expand Down
37 changes: 37 additions & 0 deletions docs/src/reductions/problem_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@
}
]
},
{
"name": "LongestCommonSubsequence",
"description": "Find the longest string that is a subsequence of every input string",
"fields": [
{
"name": "strings",
"type_name": "Vec<Vec<u8>>",
"description": "The input strings"
}
]
},
{
"name": "MaxCut",
"description": "Find maximum weight cut in a graph",
Expand Down Expand Up @@ -387,6 +398,32 @@
}
]
},
{
"name": "RuralPostman",
"description": "Find a circuit covering required edges with total length at most B (Rural Postman Problem)",
"fields": [
{
"name": "graph",
"type_name": "G",
"description": "The underlying graph G=(V,E)"
},
{
"name": "edge_weights",
"type_name": "Vec<W>",
"description": "Edge lengths l(e) for each e in E"
},
{
"name": "required_edges",
"type_name": "Vec<usize>",
"description": "Edge indices of the required subset E' ⊆ E"
},
{
"name": "bound",
"type_name": "W::Sum",
"description": "Upper bound B on total circuit length"
}
]
},
{
"name": "Satisfiability",
"description": "Find satisfying assignment for CNF formula",
Expand Down
7 changes: 7 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Flags by problem type:
BicliqueCover --left, --right, --biedges, --k
BMF --matrix (0/1), --rank
CVP --basis, --target-vec [--bounds]
RuralPostman (RPP) --graph, --edge-weights, --required-edges, --bound
LCS --strings
FVS --arcs [--weights] [--num-vertices]
ILP, CircuitSAT (via reduction only)
Expand Down Expand Up @@ -332,6 +333,12 @@ pub struct CreateArgs {
/// Variable bounds for CVP as "lower,upper" (e.g., "-10,10") [default: -10,10]
#[arg(long, allow_hyphen_values = true)]
pub bounds: Option<String>,
/// Required edge indices for RuralPostman (comma-separated, e.g., "0,2,4")
#[arg(long)]
pub required_edges: Option<String>,
/// Upper bound B for RuralPostman
#[arg(long)]
pub bound: Option<i32>,
/// Input strings for LCS (semicolon-separated, e.g., "ABAC;BACA")
#[arg(long)]
pub strings: Option<String>,
Expand Down
37 changes: 37 additions & 0 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.basis.is_none()
&& args.target_vec.is_none()
&& args.bounds.is_none()
&& args.required_edges.is_none()
&& args.bound.is_none()
&& args.strings.is_none()
&& args.arcs.is_none()
}
Expand Down Expand Up @@ -91,6 +93,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"KColoring" => "--graph 0-1,1-2,2-0 --k 3",
"PartitionIntoTriangles" => "--graph 0-1,1-2,0-2",
"Factoring" => "--target 15 --m 4 --n 4",
"RuralPostman" => {
"--graph 0-1,1-2,2-3,3-0 --edge-weights 1,1,1,1 --required-edges 0,2 --bound 4"
}
"SubsetSum" => "--sizes 3,7,1,8,2,4 --target 11",
_ => "",
}
Expand Down Expand Up @@ -232,6 +237,38 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
(data, resolved_variant.clone())
}

// RuralPostman
"RuralPostman" => {
let (graph, _) = parse_graph(args).map_err(|e| {
anyhow::anyhow!(
"{e}\n\nUsage: pred create RuralPostman --graph 0-1,1-2,2-3 --edge-weights 1,1,1 --required-edges 0,2 --bound 6"
)
})?;
let edge_weights = parse_edge_weights(args, graph.num_edges())?;
let required_edges_str = args.required_edges.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"RuralPostman requires --required-edges\n\n\
Usage: pred create RuralPostman --graph 0-1,1-2,2-3 --edge-weights 1,1,1 --required-edges 0,2 --bound 6"
)
})?;
let required_edges: Vec<usize> = util::parse_comma_list(required_edges_str)?;
let bound = args.bound.ok_or_else(|| {
anyhow::anyhow!(
"RuralPostman requires --bound\n\n\
Usage: pred create RuralPostman --graph 0-1,1-2,2-3 --edge-weights 1,1,1 --required-edges 0,2 --bound 6"
)
})?;
(
ser(RuralPostman::new(
graph,
edge_weights,
required_edges,
bound,
))?,
resolved_variant.clone(),
)
}

// KColoring
"KColoring" => {
let (graph, _) = parse_graph(args).map_err(|e| {
Expand Down
2 changes: 2 additions & 0 deletions problemreductions-cli/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub fn load_problem(
"MaxCut" => deser_opt::<MaxCut<SimpleGraph, i32>>(data),
"MaximalIS" => deser_opt::<MaximalIS<SimpleGraph, i32>>(data),
"TravelingSalesman" => deser_opt::<TravelingSalesman<SimpleGraph, i32>>(data),
"RuralPostman" => deser_sat::<RuralPostman<SimpleGraph, i32>>(data),
"KColoring" => match variant.get("k").map(|s| s.as_str()) {
Some("K3") => deser_sat::<KColoring<K3, SimpleGraph>>(data),
_ => deser_sat::<KColoring<KN, SimpleGraph>>(data),
Expand Down Expand Up @@ -276,6 +277,7 @@ pub fn serialize_any_problem(
"MaxCut" => try_ser::<MaxCut<SimpleGraph, i32>>(any),
"MaximalIS" => try_ser::<MaximalIS<SimpleGraph, i32>>(any),
"TravelingSalesman" => try_ser::<TravelingSalesman<SimpleGraph, i32>>(any),
"RuralPostman" => try_ser::<RuralPostman<SimpleGraph, i32>>(any),
"KColoring" => match variant.get("k").map(|s| s.as_str()) {
Some("K3") => try_ser::<KColoring<K3, SimpleGraph>>(any),
_ => try_ser::<KColoring<KN, SimpleGraph>>(any),
Expand Down
2 changes: 2 additions & 0 deletions problemreductions-cli/src/problem_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub const ALIASES: &[(&str, &str)] = &[
("KSAT", "KSatisfiability"),
("TSP", "TravelingSalesman"),
("CVP", "ClosestVectorProblem"),
("RPP", "RuralPostman"),
("LCS", "LongestCommonSubsequence"),
("MaxMatching", "MaximumMatching"),
("FVS", "MinimumFeedbackVertexSet"),
Expand Down Expand Up @@ -49,6 +50,7 @@ pub fn resolve_alias(input: &str) -> String {
"kcoloring" => "KColoring".to_string(),
"maximalis" => "MaximalIS".to_string(),
"travelingsalesman" | "tsp" => "TravelingSalesman".to_string(),
"ruralpostman" | "rpp" => "RuralPostman".to_string(),
"paintshop" => "PaintShop".to_string(),
"bmf" => "BMF".to_string(),
"bicliquecover" => "BicliqueCover".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub mod prelude {
pub use crate::models::graph::{
KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching,
MinimumDominatingSet, MinimumFeedbackVertexSet, MinimumVertexCover,
PartitionIntoTriangles, TravelingSalesman,
PartitionIntoTriangles, RuralPostman, TravelingSalesman,
};
pub use crate::models::misc::{
BinPacking, Factoring, Knapsack, LongestCommonSubsequence, PaintShop, SubsetSum,
Expand Down
3 changes: 3 additions & 0 deletions src/models/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! - [`TravelingSalesman`]: Traveling Salesman (minimum weight Hamiltonian cycle)
//! - [`SpinGlass`]: Ising model Hamiltonian
//! - [`BicliqueCover`]: Biclique cover on bipartite graphs
//! - [`RuralPostman`]: Rural Postman (circuit covering required edges)

pub(crate) mod biclique_cover;
pub(crate) mod graph_partitioning;
Expand All @@ -28,6 +29,7 @@ pub(crate) mod minimum_dominating_set;
pub(crate) mod minimum_feedback_vertex_set;
pub(crate) mod minimum_vertex_cover;
pub(crate) mod partition_into_triangles;
pub(crate) mod rural_postman;
pub(crate) mod spin_glass;
pub(crate) mod traveling_salesman;

Expand All @@ -43,5 +45,6 @@ pub use minimum_dominating_set::MinimumDominatingSet;
pub use minimum_feedback_vertex_set::MinimumFeedbackVertexSet;
pub use minimum_vertex_cover::MinimumVertexCover;
pub use partition_into_triangles::PartitionIntoTriangles;
pub use rural_postman::RuralPostman;
pub use spin_glass::SpinGlass;
pub use traveling_salesman::TravelingSalesman;
Loading
Loading