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
11 changes: 11 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"LongestCommonSubsequence": [Longest Common Subsequence],
"SubsetSum": [Subset Sum],
"MinimumFeedbackVertexSet": [Minimum Feedback Vertex Set],
"MinimumCutIntoBoundedSets": [Minimum Cut Into Bounded Sets],
)

// Definition label: "def:<ProblemName>" — each definition block must have a matching label
Expand Down Expand Up @@ -434,6 +435,16 @@ Graph Partitioning is a core NP-hard problem arising in VLSI design, parallel co
caption: [Graph with $n = 6$ vertices partitioned into $A = {v_0, v_1, v_2}$ (blue) and $B = {v_3, v_4, v_5}$ (red). The 3 crossing edges $(v_1, v_3)$, $(v_2, v_3)$, $(v_2, v_4)$ are shown in bold red; internal edges are gray.],
) <fig:graph-partitioning>
]
#problem-def("MinimumCutIntoBoundedSets")[
Given an undirected graph $G = (V, E)$ with edge weights $w: E -> ZZ^+$, designated vertices $s, t in V$, a positive integer $B <= |V|$, and a positive integer $K$, determine whether there exists a partition of $V$ into disjoint sets $V_1$ and $V_2$ such that $s in V_1$, $t in V_2$, $|V_1| <= B$, $|V_2| <= B$, and
$ sum_({u,v} in E: u in V_1, v in V_2) w({u,v}) <= K. $
][
Minimum Cut Into Bounded Sets (Garey & Johnson ND17) combines the classical minimum $s$-$t$ cut problem with a balance constraint on partition sizes. Without the balance constraint ($B = |V|$), the problem reduces to standard minimum $s$-$t$ cut, solvable in polynomial time via network flow. Adding the requirement $|V_1| <= B$ and $|V_2| <= B$ makes the problem NP-complete; it remains NP-complete even for $B = |V| slash 2$ and unit edge weights (the minimum bisection problem) @garey1976. Applications include VLSI layout, load balancing, and graph bisection.

The best known exact algorithm is brute-force enumeration of all $2^n$ vertex partitions in $O(2^n)$ time. For the special case of minimum bisection, Cygan et al. @cygan2014 showed fixed-parameter tractability with respect to the cut size. No polynomial-time finite approximation factor exists for balanced graph partition unless $P = N P$ (Andreev and Racke, 2006). Arora, Rao, and Vazirani @arora2009 gave an $O(sqrt(log n))$-approximation for balanced separator.

*Example.* Consider $G$ with 4 vertices and edges $(v_0, v_1)$, $(v_1, v_2)$, $(v_2, v_3)$ with unit weights, $s = v_0$, $t = v_3$, $B = 3$, $K = 1$. The partition $V_1 = {v_0, v_1}$, $V_2 = {v_2, v_3}$ gives cut weight $w({v_1, v_2}) = 1 <= K$. Both $|V_1| = 2 <= 3$ and $|V_2| = 2 <= 3$. Answer: YES.
]
#problem-def("KColoring")[
Given $G = (V, E)$ and $k$ colors, find $c: V -> {1, ..., k}$ minimizing $|{(u, v) in E : c(u) = c(v)}|$.
][
Expand Down
13 changes: 13 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Flags by problem type:
SpinGlass --graph, --couplings, --fields
KColoring --graph, --k
GraphPartitioning --graph
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound, --cut-bound
Factoring --target, --m, --n
BinPacking --sizes, --capacity
SubsetSum --sizes, --target
Expand Down Expand Up @@ -337,6 +338,18 @@ pub struct CreateArgs {
/// Directed arcs for directed graph problems (e.g., 0>1,1>2,2>0)
#[arg(long)]
pub arcs: Option<String>,
/// Source vertex index (for MinimumCutIntoBoundedSets)
#[arg(long)]
pub source: Option<usize>,
/// Sink vertex index (for MinimumCutIntoBoundedSets)
#[arg(long)]
pub sink: Option<usize>,
/// Size bound for partition sets (for MinimumCutIntoBoundedSets)
#[arg(long)]
pub size_bound: Option<usize>,
/// Cut weight bound (for MinimumCutIntoBoundedSets)
#[arg(long)]
pub cut_bound: Option<i32>,
}

#[derive(clap::Args)]
Expand Down
73 changes: 72 additions & 1 deletion problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::problem_name::{parse_problem_spec, resolve_variant};
use crate::util;
use anyhow::{bail, Context, Result};
use problemreductions::models::algebraic::{ClosestVectorProblem, BMF};
use problemreductions::models::graph::GraphPartitioning;
use problemreductions::models::graph::{GraphPartitioning, MinimumCutIntoBoundedSets};
use problemreductions::models::misc::{BinPacking, LongestCommonSubsequence, PaintShop, SubsetSum};
use problemreductions::prelude::*;
use problemreductions::registry::collect_schemas;
Expand Down Expand Up @@ -49,6 +49,10 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.bounds.is_none()
&& args.strings.is_none()
&& args.arcs.is_none()
&& args.source.is_none()
&& args.sink.is_none()
&& args.size_bound.is_none()
&& args.cut_bound.is_none()
}

fn type_format_hint(type_name: &str, graph_type: Option<&str>) -> &'static str {
Expand Down Expand Up @@ -81,6 +85,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
_ => "--graph 0-1,1-2,2-3 --weights 1,1,1,1",
},
"GraphPartitioning" => "--graph 0-1,1-2,2-3,0-2,1-3,0-3",
"MinimumCutIntoBoundedSets" => {
"--graph 0-1,1-2,2-3 --edge-weights 1,1,1 --source 0 --sink 3 --size-bound 3 --cut-bound 1"
}
"MaxCut" | "MaximumMatching" | "TravelingSalesman" => {
"--graph 0-1,1-2,2-3 --edge-weights 1,1,1"
}
Expand Down Expand Up @@ -213,6 +220,39 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// Minimum cut into bounded sets (graph + edge weights + s/t/B/K)
"MinimumCutIntoBoundedSets" => {
let (graph, _) = parse_graph(args).map_err(|e| {
anyhow::anyhow!(
"{e}\n\nUsage: pred create MinimumCutIntoBoundedSets --graph 0-1,1-2,2-3 --edge-weights 1,1,1 --source 0 --sink 2 --size-bound 2 --cut-bound 1"
)
})?;
let edge_weights = parse_edge_weights(args, graph.num_edges())?;
let source = args
.source
.context("--source is required for MinimumCutIntoBoundedSets")?;
let sink = args
.sink
.context("--sink is required for MinimumCutIntoBoundedSets")?;
let size_bound = args
.size_bound
.context("--size-bound is required for MinimumCutIntoBoundedSets")?;
let cut_bound = args
.cut_bound
.context("--cut-bound is required for MinimumCutIntoBoundedSets")?;
(
ser(MinimumCutIntoBoundedSets::new(
graph,
edge_weights,
source,
sink,
size_bound,
cut_bound,
))?,
Comment on lines +231 to +251
resolved_variant.clone(),
)
}

// Graph problems with edge weights
"MaxCut" | "MaximumMatching" | "TravelingSalesman" => {
let (graph, _) = parse_graph(args).map_err(|e| {
Expand Down Expand Up @@ -997,6 +1037,37 @@ fn create_random(
}
}

// MinimumCutIntoBoundedSets (graph + edge weights + s/t/B/K)
"MinimumCutIntoBoundedSets" => {
let edge_prob = args.edge_prob.unwrap_or(0.5);
if !(0.0..=1.0).contains(&edge_prob) {
bail!("--edge-prob must be between 0.0 and 1.0");
}
let graph = util::create_random_graph(num_vertices, edge_prob, args.seed);
let num_edges = graph.num_edges();
let edge_weights = vec![1i32; num_edges];
let source = 0;
let sink = if num_vertices > 1 {
num_vertices - 1
} else {
0
};
Comment on lines +1046 to +1054
let size_bound = num_vertices; // no effective size constraint
let cut_bound = num_edges as i32; // generous bound
let variant = variant_map(&[("graph", "SimpleGraph"), ("weight", "i32")]);
(
ser(MinimumCutIntoBoundedSets::new(
graph,
edge_weights,
source,
sink,
size_bound,
cut_bound,
))?,
variant,
)
}

// GraphPartitioning (graph only, no weights; requires even vertex count)
"GraphPartitioning" => {
let num_vertices = if num_vertices % 2 != 0 {
Expand Down
4 changes: 4 additions & 0 deletions problemreductions-cli/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ pub fn load_problem(
"Knapsack" => deser_opt::<Knapsack>(data),
"LongestCommonSubsequence" => deser_opt::<LongestCommonSubsequence>(data),
"MinimumFeedbackVertexSet" => deser_opt::<MinimumFeedbackVertexSet<i32>>(data),
"MinimumCutIntoBoundedSets" => {
deser_sat::<MinimumCutIntoBoundedSets<SimpleGraph, i32>>(data)
}
"SubsetSum" => deser_sat::<SubsetSum>(data),
_ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)),
}
Expand Down Expand Up @@ -312,6 +315,7 @@ pub fn serialize_any_problem(
"Knapsack" => try_ser::<Knapsack>(any),
"LongestCommonSubsequence" => try_ser::<LongestCommonSubsequence>(any),
"MinimumFeedbackVertexSet" => try_ser::<MinimumFeedbackVertexSet<i32>>(any),
"MinimumCutIntoBoundedSets" => try_ser::<MinimumCutIntoBoundedSets<SimpleGraph, i32>>(any),
"SubsetSum" => try_ser::<SubsetSum>(any),
_ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)),
}
Expand Down
1 change: 1 addition & 0 deletions problemreductions-cli/src/problem_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub fn resolve_alias(input: &str) -> String {
"knapsack" => "Knapsack".to_string(),
"lcs" | "longestcommonsubsequence" => "LongestCommonSubsequence".to_string(),
"fvs" | "minimumfeedbackvertexset" => "MinimumFeedbackVertexSet".to_string(),
"minimumcutintoboundedsets" => "MinimumCutIntoBoundedSets".to_string(), // G&J ND17
"subsetsum" => "SubsetSum".to_string(),
_ => input.to_string(), // pass-through for exact names
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ pub mod prelude {
pub use crate::models::graph::{BicliqueCover, GraphPartitioning, SpinGlass};
pub use crate::models::graph::{
KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching,
MinimumDominatingSet, MinimumFeedbackVertexSet, MinimumVertexCover, TravelingSalesman,
MinimumCutIntoBoundedSets, MinimumDominatingSet, MinimumFeedbackVertexSet,
MinimumVertexCover, TravelingSalesman,
};
pub use crate::models::misc::{
BinPacking, Factoring, Knapsack, LongestCommonSubsequence, PaintShop, SubsetSum,
Expand Down
Loading
Loading