Skip to content
Merged
11 changes: 11 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"SubsetSum": [Subset Sum],
"MinimumFeedbackArcSet": [Minimum Feedback Arc Set],
"MinimumFeedbackVertexSet": [Minimum Feedback Vertex Set],
"MinimumCutIntoBoundedSets": [Minimum Cut Into Bounded Sets],
"MultipleChoiceBranching": [Multiple Choice Branching],
"PartitionIntoPathsOfLength2": [Partition into Paths of Length 2],
"ResourceConstrainedScheduling": [Resource Constrained Scheduling],
Expand Down Expand Up @@ -569,6 +570,16 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
]
]
}
#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("BiconnectivityAugmentation")[
Given an undirected graph $G = (V, E)$, a set $F$ of candidate edges on $V$ with $F inter E = emptyset$, weights $w: F -> RR$, and a budget $B in RR$, find $F' subset.eq F$ such that $sum_(e in F') w(e) <= B$ and the augmented graph $G' = (V, E union F')$ is biconnected, meaning $G'$ is connected and deleting any single vertex leaves it connected.
][
Expand Down
11 changes: 9 additions & 2 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ Flags by problem type:
PartitionIntoTriangles --graph
GraphPartitioning --graph
GeneralizedHex --graph, --source, --sink
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound, --cut-bound
HamiltonianCircuit, HC --graph
BoundedComponentSpanningForest --graph, --weights, --k, --bound
UndirectedTwoCommodityIntegralFlow --graph, --capacities, --source-1, --sink-1, --source-2, --sink-2, --requirement-1, --requirement-2
Expand Down Expand Up @@ -328,10 +329,10 @@ pub struct CreateArgs {
/// Edge capacities for multicommodity flow problems (e.g., 1,1,2)
#[arg(long)]
pub capacities: Option<String>,
/// Source vertex for path-based graph problems
/// Source vertex for path-based graph problems and MinimumCutIntoBoundedSets
#[arg(long)]
pub source: Option<usize>,
/// Sink vertex for path-based graph problems
/// Sink vertex for path-based graph problems and MinimumCutIntoBoundedSets
#[arg(long)]
pub sink: Option<usize>,
/// Required number of paths for LengthBoundedDisjointPaths
Expand Down Expand Up @@ -478,6 +479,12 @@ pub struct CreateArgs {
/// Directed arcs for directed graph problems (e.g., 0>1,1>2,2>0)
#[arg(long)]
pub arcs: Option<String>,
/// 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>,
/// Item values (e.g., 3,4,5,7) for PartiallyOrderedKnapsack
#[arg(long)]
pub values: Option<String>,
Expand Down
75 changes: 73 additions & 2 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use problemreductions::export::{ModelExample, ProblemRef, ProblemSide, RuleExamp
use problemreductions::models::algebraic::{ClosestVectorProblem, ConsecutiveOnesSubmatrix, BMF};
use problemreductions::models::graph::{
GeneralizedHex, GraphPartitioning, HamiltonianCircuit, HamiltonianPath,
LengthBoundedDisjointPaths, MinimumMultiwayCut, MultipleChoiceBranching, SteinerTree,
StrongConnectivityAugmentation,
LengthBoundedDisjointPaths, MinimumCutIntoBoundedSets, MinimumMultiwayCut,
MultipleChoiceBranching, SteinerTree, StrongConnectivityAugmentation,
};
use problemreductions::models::misc::{
BinPacking, CbqRelation, ConjunctiveBooleanQuery, FlowShopScheduling, LongestCommonSubsequence,
Expand Down Expand Up @@ -86,6 +86,10 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.pattern.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()
&& args.values.is_none()
&& args.precedences.is_none()
&& args.distance_matrix.is_none()
Expand Down Expand Up @@ -290,6 +294,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
},
"GraphPartitioning" => "--graph 0-1,1-2,2-3,0-2,1-3,0-3",
"GeneralizedHex" => "--graph 0-1,0-2,0-3,1-4,2-4,3-4,4-5 --source 0 --sink 5",
"MinimumCutIntoBoundedSets" => {
"--graph 0-1,1-2,2-3 --edge-weights 1,1,1 --source 0 --sink 3 --size-bound 3 --cut-bound 1"
}
"BoundedComponentSpanningForest" => {
"--graph 0-1,1-2,2-3,3-4,4-5,5-6,6-7,0-7,1-5,2-6 --weights 2,3,1,2,3,1,2,1 --k 3 --bound 6"
}
Expand Down Expand Up @@ -726,6 +733,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 +744 to +764
resolved_variant.clone(),
)
}

// Hamiltonian Circuit (graph only, no weights)
"HamiltonianCircuit" => {
let (graph, _) = parse_graph(args).map_err(|e| {
Expand Down Expand Up @@ -3616,6 +3656,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 +3665 to +3673
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
6 changes: 5 additions & 1 deletion problemreductions-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2821,7 +2821,11 @@ fn test_create_model_example_steiner_tree() {
#[test]
fn test_create_missing_model_example() {
let output = pred()
.args(["create", "--example", "MaximumIndependentSet/KingsSubgraph/One"])
.args([
"create",
"--example",
"MaximumIndependentSet/KingsSubgraph/One",
])
.output()
.unwrap();
assert!(!output.status.success());
Expand Down
9 changes: 5 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ pub mod prelude {
};
pub use crate::models::graph::{
KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching,
MinimumDominatingSet, MinimumFeedbackArcSet, MinimumFeedbackVertexSet, MinimumMultiwayCut,
MinimumSumMulticenter, MinimumVertexCover, MultipleChoiceBranching,
OptimalLinearArrangement, PartitionIntoPathsOfLength2, PartitionIntoTriangles,
RuralPostman, TravelingSalesman, UndirectedTwoCommodityIntegralFlow,
MinimumCutIntoBoundedSets, MinimumDominatingSet, MinimumFeedbackArcSet,
MinimumFeedbackVertexSet, MinimumMultiwayCut, MinimumSumMulticenter, MinimumVertexCover,
MultipleChoiceBranching, OptimalLinearArrangement, PartitionIntoPathsOfLength2,
PartitionIntoTriangles, RuralPostman, TravelingSalesman,
UndirectedTwoCommodityIntegralFlow,
};
pub use crate::models::misc::{
BinPacking, CbqRelation, ConjunctiveBooleanQuery, ConjunctiveQueryFoldability, Factoring,
Expand Down
Loading
Loading