Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c98aab7
Add plan for #184: MinimumMultiwayCut model
hmyuuu Mar 10, 2026
b4c7589
feat: add MinimumMultiwayCut model (#184)
hmyuuu Mar 10, 2026
4e30437
fix: CLI support, paper entry, and QA for MinimumMultiwayCut (#184)
hmyuuu Mar 10, 2026
b98c68e
docs: improve MinimumMultiwayCut discoverability and update CLI docs
hmyuuu Mar 10, 2026
c81bcac
fix: use defensive bounds checking in MinimumMultiwayCut evaluate
hmyuuu Mar 10, 2026
069b5f1
Merge origin/main into pr-221 (MinimumMultiwayCut)
zazabap Mar 15, 2026
0310fd8
fix: resolve merge conflicts with main and update ProblemSchemaEntry
zazabap Mar 15, 2026
c1e95f9
chore: remove per-model example file (not needed)
zazabap Mar 15, 2026
5c9bcd7
fix: address structural review findings
zazabap Mar 15, 2026
ea8df53
fix: add coverage tests and CLI terminal validation for MinimumMultiw…
zazabap Mar 16, 2026
4e2468c
Merge main: resolve conflicts with SteinerTree and SequencingWithinIn…
zazabap Mar 16, 2026
629235d
fix: data-driven paper example, CLI tests, dispatch cleanup for Minim…
zazabap Mar 16, 2026
41509df
fix: remove duplicate terminals field and parse_terminals from merge
zazabap Mar 16, 2026
9a823e5
Merge main: resolve conflicts with new models added since PR opened
GiggleLiu Mar 16, 2026
cf08d9e
fix: remove duplicate terminals field and Vec<u64> match arm
zazabap Mar 17, 2026
3c18df2
Merge origin/main into issue-184-minimum-multiway-cut
zazabap Mar 17, 2026
d4f4a77
fix: update pred list output in cli.md to match current state
zazabap Mar 17, 2026
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
45 changes: 45 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"BoundedComponentSpanningForest": [Bounded Component Spanning Forest],
"BinPacking": [Bin Packing],
"ClosestVectorProblem": [Closest Vector Problem],
"MinimumMultiwayCut": [Minimum Multiway Cut],
"OptimalLinearArrangement": [Optimal Linear Arrangement],
"RuralPostman": [Rural Postman],
"LongestCommonSubsequence": [Longest Common Subsequence],
Expand Down Expand Up @@ -1069,6 +1070,50 @@ is feasible: each set induces a connected subgraph, the component weights are $2
]
]
}
#{
let x = load-model-example("MinimumMultiwayCut")
let nv = graph-num-vertices(x.instance)
let ne = graph-num-edges(x.instance)
let edges = x.instance.graph.inner.edges.map(e => (e.at(0), e.at(1)))
let weights = x.instance.edge_weights
let terminals = x.instance.terminals
let sol = x.optimal.at(0)
let cut-edge-indices = sol.config.enumerate().filter(((i, v)) => v == 1).map(((i, _)) => i)
let cut-edges = cut-edge-indices.map(i => edges.at(i))
let cost = sol.metric.Valid
[
#problem-def("MinimumMultiwayCut")[
Given an undirected graph $G=(V,E)$ with edge weights $w: E -> RR_(>0)$ and a set of $k$ terminal vertices $T = {t_1, ..., t_k} subset.eq V$, find a minimum-weight set of edges $C subset.eq E$ such that no two terminals remain in the same connected component of $G' = (V, E backslash C)$.
][
The Minimum Multiway Cut problem generalizes the classical minimum $s$-$t$ cut: for $k=2$ it reduces to max-flow and is solvable in polynomial time, but for $k >= 3$ on general graphs it becomes NP-hard @dahlhaus1994. The problem arises in VLSI design, image segmentation, and network design. A $(2 - 2 slash k)$-approximation is achievable in polynomial time by taking the union of the $k - 1$ cheapest isolating cuts @dahlhaus1994. The best known exact algorithm runs in $O^*(1.84^k)$ time (suppressing polynomial factors) via submodular functions on isolating cuts @cao2013.

*Example.* Consider a graph with $n = #nv$ vertices, $m = #ne$ edges, and $k = #terminals.len()$ terminals $T = {#terminals.map(t => $#t$).join(", ")}$, with edge weights #edges.zip(weights).map(((e, w)) => $w(#(e.at(0)), #(e.at(1))) = #w$).join(", "). The optimal multiway cut removes edges ${#cut-edges.map(e => $(#(e.at(0)), #(e.at(1)))$).join(", ")}$ with total weight #cut-edge-indices.map(i => $#(weights.at(i))$).join($+$) $= #cost$, placing each terminal in a distinct component.

#figure({
let verts = ((0, 0.8), (1.2, 1.5), (2.4, 0.8), (1.8, -0.2), (0.6, -0.2))
canvas(length: 1cm, {
for (idx, (u, v)) in edges.enumerate() {
let is-cut = cut-edge-indices.contains(idx)
g-edge(verts.at(u), verts.at(v),
stroke: if is-cut { (paint: red, thickness: 2pt, dash: "dashed") } else { 1pt + luma(120) })
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 dy = if idx == 5 { 0.15 } else { 0 }
draw.content((mx, my + dy), text(7pt, fill: luma(80))[#weights.at(idx)])
}
for (k, pos) in verts.enumerate() {
let is-terminal = terminals.contains(k)
g-node(pos, name: "v" + str(k),
fill: if is-terminal { graph-colors.at(0) } else { luma(180) },
label: text(fill: white)[$#k$])
}
})
},
caption: [Minimum Multiway Cut with terminals ${#terminals.map(t => $#t$).join(", ")}$ (blue). Dashed red edges form the optimal cut (weight #cost).],
) <fig:multiway-cut>
]
]
}
#problem-def("OptimalLinearArrangement")[
Given an undirected graph $G=(V,E)$ and a non-negative integer $K$, is there a bijection $f: V -> {0, 1, dots, |V|-1}$ such that $sum_({u,v} in E) |f(u) - f(v)| <= K$?
][
Expand Down
20 changes: 20 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,26 @@ @article{ibarra1975
doi = {10.1145/321906.321909}
}

@article{dahlhaus1994,
author = {Elias Dahlhaus and David S. Johnson and Christos H. Papadimitriou and Paul D. Seymour and Mihalis Yannakakis},
title = {The Complexity of Multiterminal Cuts},
journal = {SIAM Journal on Computing},
volume = {23},
number = {4},
pages = {864--894},
year = {1994},
doi = {10.1137/S0097539292225297}
}

@inproceedings{cao2013,
author = {Yixin Cao and Jianer Chen and Jianxin Wang},
title = {An Improved Fixed-Parameter Algorithm for the Minimum Weight Multiway Cut Problem},
booktitle = {Fundamentals of Computation Theory (FCT 2013)},
pages = {96--107},
year = {2013},
doi = {10.1007/978-3-642-40164-0_11}
}

@article{maier1978,
author = {David Maier},
title = {The Complexity of Some Problems on Subsequences and Supersequences},
Expand Down
102 changes: 79 additions & 23 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,82 @@ Lists all registered problem types with their short aliases.

```bash
$ pred list
Registered problems: 17 types, 48 reductions, 25 variant nodes

Problem Aliases Variants Reduces to
───────────────────── ────────── ──────── ──────────
CircuitSAT 1 1
Factoring 1 2
ILP 1 1
KColoring 2 3
KSatisfiability 3SAT, KSAT 3 7
MaxCut 1 1
MaximumClique 1 1
MaximumIndependentSet MIS 4 10
MaximumMatching 1 2
MaximumSetPacking 2 4
MinimumDominatingSet 1 1
MinimumSetCovering 1 1
MinimumVertexCover MVC 1 4
QUBO 1 1
Satisfiability SAT 1 5
SpinGlass 2 3
TravelingSalesman TSP 1 1

Use `pred show <problem>` to see variants, reductions, and fields.
Registered problems: 50 types, 59 reductions, 69 variant nodes

Problem Aliases Rules Complexity
──────────────────────────────────────────────── ─────────── ───── ──────────────────────────────────────────────────────────────────
BMF * O(2^(cols * rank + rank * rows))
BicliqueCover * O(2^num_vertices)
BiconnectivityAugmentation/SimpleGraph/i32 * O(2^num_potential_edges)
BinPacking/f64 1 O(2^num_items)
BinPacking/i32 * O(2^num_items)
BoundedComponentSpanningForest/SimpleGraph/i32 * O(3^num_vertices)
CircuitSAT * 2 O(2^num_variables)
ClosestVectorProblem/f64 CVP O(2^num_basis_vectors)
ClosestVectorProblem/i32 * O(2^num_basis_vectors)
DirectedTwoCommodityIntegralFlow * D2CIF O((max_capacity + 1)^(2 * num_arcs))
ExactCoverBy3Sets * X3C O(2^universe_size)
Factoring * 2 O(exp((m + n)^0.3333333333333333 * log(m + n)^0.6666666666666666))
FlowShopScheduling * O(factorial(num_jobs))
GraphPartitioning/SimpleGraph * O(2^num_vertices)
HamiltonianPath/SimpleGraph * O(1.657^num_vertices)
ILP/bool * 2 O(2^num_vars)
ILP/i32 O(num_vars^num_vars)
IsomorphicSpanningTree * O(factorial(num_vertices))
KColoring/SimpleGraph/KN * 3 O(2^num_vertices)
KColoring/SimpleGraph/K2 O(num_edges + num_vertices)
KColoring/SimpleGraph/K3 O(1.3289^num_vertices)
KColoring/SimpleGraph/K4 O(1.7159^num_vertices)
KColoring/SimpleGraph/K5 O(2^num_vertices)
KSatisfiability/KN * KSAT 6 O(2^num_variables)
KSatisfiability/K2 O(num_clauses + num_variables)
KSatisfiability/K3 O(1.307^num_variables)
Knapsack * 1 O(2^(0.5 * num_items))
LengthBoundedDisjointPaths/SimpleGraph * O(2^(num_paths_required * num_vertices))
LongestCommonSubsequence * LCS 1 O(2^min_string_length)
MaxCut/SimpleGraph/i32 * 1 O(2^(0.7906666666666666 * num_vertices))
MaximalIS/SimpleGraph/i32 * O(3^(0.3333333333333333 * num_vertices))
MaximumClique/SimpleGraph/i32 * 2 O(1.1996^num_vertices)
MaximumIndependentSet/SimpleGraph/One * MIS 14 O(1.1996^num_vertices)
MaximumIndependentSet/KingsSubgraph/One O(2^sqrt(num_vertices))
MaximumIndependentSet/SimpleGraph/i32 O(1.1996^num_vertices)
MaximumIndependentSet/UnitDiskGraph/One O(2^sqrt(num_vertices))
MaximumIndependentSet/KingsSubgraph/i32 O(2^sqrt(num_vertices))
MaximumIndependentSet/TriangularSubgraph/i32 O(2^sqrt(num_vertices))
MaximumIndependentSet/UnitDiskGraph/i32 O(2^sqrt(num_vertices))
MaximumMatching/SimpleGraph/i32 * MaxMatching 2 O(num_vertices^3)
MaximumSetPacking/One * 6 O(2^num_sets)
MaximumSetPacking/f64 O(2^num_sets)
MaximumSetPacking/i32 O(2^num_sets)
MinimumDominatingSet/SimpleGraph/i32 * 1 O(1.4969^num_vertices)
MinimumFeedbackArcSet/i32 * FAS O(2^num_vertices)
MinimumFeedbackVertexSet/i32 * FVS O(1.9977^num_vertices)
MinimumMultiwayCut/SimpleGraph/i32 * O(num_vertices^3 * 1.84^num_terminals)
MinimumSetCovering/i32 * 1 O(2^num_sets)
MinimumSumMulticenter/SimpleGraph/i32 * pmedian O(2^num_vertices)
MinimumTardinessSequencing * O(2^num_tasks)
MinimumVertexCover/SimpleGraph/i32 * MVC 2 O(1.1996^num_vertices)
MultipleChoiceBranching/i32 * O(2^num_arcs)
OptimalLinearArrangement/SimpleGraph * OLA O(2^num_vertices)
PaintShop * O(2^num_cars)
PartitionIntoTriangles/SimpleGraph * O(2^num_vertices)
QUBO/f64 * 2 O(2^num_vars)
RuralPostman/SimpleGraph/i32 * RPP O(num_vertices^2 * 2^num_vertices)
Satisfiability * SAT 5 O(2^num_variables)
SequencingWithinIntervals * O(2^num_tasks)
SetBasis * O(2^(basis_size * universe_size))
ShortestCommonSupersequence * SCS O(alphabet_size^bound)
SpinGlass/SimpleGraph/f64 3 O(2^num_spins)
SpinGlass/SimpleGraph/i32 * O(2^num_spins)
SteinerTree/SimpleGraph/One O(num_vertices * 3^num_terminals)
SteinerTree/SimpleGraph/i32 * O(num_vertices * 3^num_terminals)
SubgraphIsomorphism * O(num_host_vertices^num_pattern_vertices)
SubsetSum * O(2^(0.5 * num_elements))
TravelingSalesman/SimpleGraph/i32 * TSP 2 O(2^num_vertices)
UndirectedTwoCommodityIntegralFlow * O(5^num_edges)

* = default variant
Use `pred show <problem>` to see reductions and fields.
```

### `pred show` — Inspect a problem
Expand Down Expand Up @@ -291,6 +344,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 MinimumMultiwayCut --graph 0-1,1-2,2-3,3-0 --terminals 0,2 --edge-weights 3,1,2,4 -o mmc.json
pred create SteinerTree --graph 0-1,0-3,1-2,1-3,2-3,2-4,3-4 --edge-weights 2,5,2,1,5,6,1 --terminals 0,2,4 -o steiner.json
pred create UndirectedTwoCommodityIntegralFlow --graph 0-2,1-2,2-3 --capacities 1,1,2 --source-1 0 --sink-1 3 --source-2 1 --sink-2 3 --requirement-1 1 --requirement-2 1 -o utcif.json
pred create LengthBoundedDisjointPaths --graph 0-1,1-6,0-2,2-3,3-6,0-4,4-5,5-6 --source 0 --sink 6 --num-paths-required 2 --bound 3 -o lbdp.json
Expand Down Expand Up @@ -511,6 +565,8 @@ You can use short aliases instead of full problem names (shown in `pred list`):
| `SAT` | `Satisfiability` |
| `3SAT` / `KSAT` | `KSatisfiability` |
| `TSP` | `TravelingSalesman` |
| `CVP` | `ClosestVectorProblem` |
| `MaxMatching` | `MaximumMatching` |

You can also specify variants with a slash: `MIS/UnitDiskGraph`, `SpinGlass/SimpleGraph`.

Expand Down
3 changes: 2 additions & 1 deletion problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Flags by problem type:
QUBO --matrix
SpinGlass --graph, --couplings, --fields
KColoring --graph, --k
MinimumMultiwayCut --graph, --terminals, --edge-weights
PartitionIntoTriangles --graph
GraphPartitioning --graph
BoundedComponentSpanningForest --graph, --weights, --k, --bound
Expand Down Expand Up @@ -419,7 +420,7 @@ pub struct CreateArgs {
/// Processing lengths for SequencingWithinIntervals (comma-separated, e.g., "3,1,1")
#[arg(long)]
pub lengths: Option<String>,
/// Terminal vertices for SteinerTree (comma-separated indices, e.g., "0,2,4")
/// Terminal vertices for SteinerTree or MinimumMultiwayCut (comma-separated indices, e.g., "0,2,4")
#[arg(long)]
pub terminals: Option<String>,
/// Tree edge list for IsomorphicSpanningTree (e.g., 0-1,1-2,2-3)
Expand Down
26 changes: 21 additions & 5 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use anyhow::{bail, Context, Result};
use problemreductions::export::{ModelExample, ProblemRef, ProblemSide, RuleExample};
use problemreductions::models::algebraic::{ClosestVectorProblem, BMF};
use problemreductions::models::graph::{
GraphPartitioning, HamiltonianPath, LengthBoundedDisjointPaths, MultipleChoiceBranching,
SteinerTree,
GraphPartitioning, HamiltonianPath, LengthBoundedDisjointPaths, MinimumMultiwayCut,
MultipleChoiceBranching, SteinerTree,
};
use problemreductions::models::misc::{
BinPacking, FlowShopScheduling, LongestCommonSubsequence, MinimumTardinessSequencing,
Expand Down Expand Up @@ -223,6 +223,7 @@ fn type_format_hint(type_name: &str, graph_type: Option<&str>) -> &'static str {
},
"Vec<u64>" => "comma-separated integers: 1,1,2",
"Vec<W>" => "comma-separated: 1,2,3",
"Vec<usize>" => "comma-separated indices: 0,2,4",
"Vec<(usize, usize, W)>" | "Vec<(usize,usize,W)>" => {
"comma-separated weighted edges: 0-2:3,1-3:5"
}
Expand Down Expand Up @@ -279,6 +280,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
}
"PartitionIntoTriangles" => "--graph 0-1,1-2,0-2",
"Factoring" => "--target 15 --m 4 --n 4",
"MinimumMultiwayCut" => "--graph 0-1,1-2,2-3 --terminals 0,2 --edge-weights 1,1,1",
"SequencingWithinIntervals" => "--release-times 0,0,5 --deadlines 11,11,6 --lengths 3,1,1",
"SteinerTree" => "--graph 0-1,1-2,1-3,3-4 --edge-weights 2,2,1,1 --terminals 0,2,4",
"OptimalLinearArrangement" => "--graph 0-1,1-2,2-3 --bound 5",
Expand Down Expand Up @@ -1181,6 +1183,21 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// MinimumMultiwayCut
"MinimumMultiwayCut" => {
let (graph, _) = parse_graph(args).map_err(|e| {
anyhow::anyhow!(
"{e}\n\nUsage: pred create MinimumMultiwayCut --graph 0-1,1-2,2-3 --terminals 0,2 [--edge-weights 1,1,1]"
)
})?;
let terminals = parse_terminals(args, graph.num_vertices())?;
let edge_weights = parse_edge_weights(args, graph.num_edges())?;
(
ser(MinimumMultiwayCut::new(graph, terminals, edge_weights))?,
resolved_variant.clone(),
)
}

// MinimumTardinessSequencing
"MinimumTardinessSequencing" => {
let deadlines_str = args.deadlines.as_deref().ok_or_else(|| {
Expand Down Expand Up @@ -1794,7 +1811,7 @@ fn parse_terminals(args: &CreateArgs, num_vertices: usize) -> Result<Vec<usize>>
let s = args
.terminals
.as_deref()
.ok_or_else(|| anyhow::anyhow!("SteinerTree requires --terminals (e.g., \"0,2,4\")"))?;
.ok_or_else(|| anyhow::anyhow!("--terminals required (e.g., \"0,2,4\")"))?;
let terminals: Vec<usize> = s
.split(',')
.map(|t| t.trim().parse::<usize>())
Expand Down Expand Up @@ -2514,8 +2531,8 @@ fn create_random(

#[cfg(test)]
mod tests {
use super::*;
use super::problem_help_flag_name;
use super::*;

#[test]
fn test_problem_help_uses_bound_for_length_bounded_disjoint_paths() {
Expand All @@ -2538,7 +2555,6 @@ mod tests {
);
}


fn empty_args() -> CreateArgs {
CreateArgs {
problem: Some("BiconnectivityAugmentation".to_string()),
Expand Down
Loading
Loading