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
66 changes: 66 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"ConsecutiveBlockMinimization": [Consecutive Block Minimization],
"ConsecutiveOnesSubmatrix": [Consecutive Ones Submatrix],
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
"IntegralFlowWithMultipliers": [Integral Flow With Multipliers],
"MinMaxMulticenter": [Min-Max Multicenter],
"FlowShopScheduling": [Flow Shop Scheduling],
"MinimumCutIntoBoundedSets": [Minimum Cut Into Bounded Sets],
Expand Down Expand Up @@ -5251,6 +5252,71 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
) <fig:d2cif>
]

#{
let x = load-model-example("IntegralFlowWithMultipliers")
let config = x.optimal_config
[
#problem-def("IntegralFlowWithMultipliers")[
Given a directed graph $G = (V, A)$, distinguished vertices $s, t in V$, arc capacities $c: A -> ZZ^+$, vertex multipliers $h: V backslash {s, t} -> ZZ^+$, and a requirement $R in ZZ^+$, determine whether there exists an integral flow function $f: A -> ZZ_(>= 0)$ such that (1) $f(a) <= c(a)$ for every $a in A$, (2) for each nonterminal vertex $v in V backslash {s, t}$, the value $h(v)$ times the total inflow into $v$ equals the total outflow from $v$, and (3) the net flow into $t$ is at least $R$.
][
Integral Flow With Multipliers is Garey and Johnson's gain/loss network problem ND33 @garey1979. Sahni includes the same integral vertex-multiplier formulation among his computationally related problems, where partition-style reductions show that adding discrete gain factors destroys the ordinary max-flow structure @sahni1974. The key wrinkle is that conservation is no longer symmetric: one unit entering a vertex may force several units to leave, so the feasible integral solutions behave more like multiplicative gadgets than classical flow balances.

When every multiplier equals $1$, the model collapses to ordinary single-commodity max flow and becomes polynomial-time solvable by the standard network-flow machinery summarized in Garey and Johnson @garey1979. Jewell studies a different continuous flow-with-gains model in which gain factors live on arcs and the flow may be fractional @jewell1962. That continuous relaxation remains polynomially tractable, so it should not be conflated with the NP-complete integral vertex-multiplier decision problem catalogued here. In this implementation the witness stores one bounded integer variable per arc, giving the direct exact-search bound $O((C + 1)^m)$ where $m = |A|$ and $C = max_(a in A) c(a)$.

*Example.* The canonical fixture encodes the Partition multiset ${2, 3, 4, 5, 6, 4}$ using source $s = v_0$, sink $t = v_7$, six unit-capacity arcs out of $s$, six sink arcs with capacities $(2, 3, 4, 5, 6, 4)$, and multipliers $(2, 3, 4, 5, 6, 4)$ on the intermediate vertices. Setting the source arcs to $v_1$, $v_3$, and $v_5$ to $1$ forces outgoing sink arcs of $2$, $4$, and $6$, respectively. The sink therefore receives net inflow $2 + 4 + 6 = 12$, exactly meeting the requirement $R = 12$.

#pred-commands(
"pred create --example IntegralFlowWithMultipliers -o integral-flow-with-multipliers.json",
"pred solve integral-flow-with-multipliers.json --solver brute-force",
"pred evaluate integral-flow-with-multipliers.json --config " + config.map(str).join(","),
)

#figure(
canvas(length: 0.9cm, {
import draw: *
let blue = graph-colors.at(0)
let gray = luma(180)
let source = (0, 0)
let sink = (6, 0)
let mids = (
(2.4, 2.5),
(2.4, 1.5),
(2.4, 0.5),
(2.4, -0.5),
(2.4, -1.5),
(2.4, -2.5),
)
let labels = (
[$v_1, h = 2$],
[$v_2, h = 3$],
[$v_3, h = 4$],
[$v_4, h = 5$],
[$v_5, h = 6$],
[$v_6, h = 4$],
)
let active = (0, 2, 4)

for (i, pos) in mids.enumerate() {
let chosen = active.contains(i)
let color = if chosen { blue } else { gray }
let thickness = if chosen { 1.3pt } else { 0.6pt }
line(source, pos, stroke: (paint: color, thickness: thickness), mark: (end: "straight", scale: 0.45))
line(pos, sink, stroke: (paint: color, thickness: thickness), mark: (end: "straight", scale: 0.45))
circle(pos, radius: 0.22, fill: if chosen { blue.lighten(75%) } else { white }, stroke: 0.6pt)
content((pos.at(0) + 0.85, pos.at(1)), text(6.5pt, labels.at(i)))
}

circle(source, radius: 0.24, fill: blue.lighten(75%), stroke: 0.6pt)
circle(sink, radius: 0.24, fill: blue.lighten(75%), stroke: 0.6pt)
content(source, text(7pt, [$s = v_0$]))
content(sink, text(7pt, [$t = v_7$]))
}),
caption: [Integral Flow With Multipliers: the blue branches send one unit from $s$ into $v_1$, $v_3$, and $v_5$, forcing sink inflow $2 + 4 + 6 = 12$ at $t$.],
) <fig:ifwm>
]
]
}

#problem-def("AdditionalKey")[
Given a set $A$ of attribute names, a collection $F$ of functional dependencies on $A$,
a subset $R subset.eq A$, and a set $K$ of candidate keys for the relational scheme $chevron.l R, F chevron.r$,
Expand Down
21 changes: 21 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,27 @@ @article{evenItaiShamir1976
doi = {10.1137/0205048}
}

@article{sahni1974,
author = {Sartaj Sahni},
title = {Computationally Related Problems},
journal = {SIAM Journal on Computing},
volume = {3},
number = {4},
pages = {262--279},
year = {1974}
}

@article{jewell1962,
author = {William S. Jewell},
title = {Optimal Flow Through Networks with Gains},
journal = {Operations Research},
volume = {10},
number = {4},
pages = {476--499},
year = {1962},
doi = {10.1287/opre.10.4.476}
}

@article{abdelWahabKameda1978,
author = {H. M. Abdel-Wahab and T. Kameda},
title = {Scheduling to Minimize Maximum Cumulative Cost Subject to Series-Parallel Precedence Constraints},
Expand Down
8 changes: 8 additions & 0 deletions problemreductions-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ Flags by problem type:
PartitionIntoTriangles --graph
GraphPartitioning --graph
GeneralizedHex --graph, --source, --sink
IntegralFlowWithMultipliers --arcs, --capacities, --source, --sink, --multipliers, --requirement
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound, --cut-bound
HamiltonianCircuit, HC --graph
LongestCircuit --graph, --edge-weights, --bound
Expand Down Expand Up @@ -313,6 +314,7 @@ Examples:
pred create SAT --num-vars 3 --clauses \"1,2;-1,3\"
pred create QUBO --matrix \"1,0.5;0.5,2\"
pred create GeneralizedHex --graph 0-1,0-2,0-3,1-4,2-4,3-4,4-5 --source 0 --sink 5
pred create IntegralFlowWithMultipliers --arcs \"0>1,0>2,1>3,2>3\" --capacities 1,1,2,2 --source 0 --sink 3 --multipliers 1,2,3,1 --requirement 2
pred create MultipleChoiceBranching/i32 --arcs \"0>1,0>2,1>3,2>3,1>4,3>5,4>5,2>4\" --weights 3,2,4,1,2,3,1,3 --partition \"0,1;2,3;4,7;5,6\" --bound 10
pred create StringToStringCorrection --source-string \"0,1,2,3,1,0\" --target-string \"0,1,3,2,1\" --bound 2 | pred solve - --solver brute-force
pred create MIS/KingsSubgraph --positions \"0,0;1,0;1,1;0,1\"
Expand Down Expand Up @@ -356,12 +358,18 @@ pub struct CreateArgs {
/// Edge capacities for multicommodity flow problems (e.g., 1,1,2)
#[arg(long)]
pub capacities: Option<String>,
/// Vertex multipliers in vertex order (e.g., 1,2,3,1)
#[arg(long)]
pub multipliers: Option<String>,
/// Source vertex for path-based graph problems and MinimumCutIntoBoundedSets
#[arg(long)]
pub source: Option<usize>,
/// Sink vertex for path-based graph problems and MinimumCutIntoBoundedSets
#[arg(long)]
pub sink: Option<usize>,
/// Required sink inflow for IntegralFlowWithMultipliers
#[arg(long)]
pub requirement: Option<u64>,
/// Required number of paths for LengthBoundedDisjointPaths
#[arg(long)]
pub num_paths_required: Option<usize>,
Expand Down
96 changes: 96 additions & 0 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
&& args.edge_weights.is_none()
&& args.edge_lengths.is_none()
&& args.capacities.is_none()
&& args.multipliers.is_none()
&& args.source.is_none()
&& args.sink.is_none()
&& args.requirement.is_none()
&& args.num_paths_required.is_none()
&& args.couplings.is_none()
&& args.fields.is_none()
Expand Down Expand Up @@ -513,6 +515,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"KClique" => "--graph 0-1,0-2,1-3,2-3,2-4,3-4 --k 3",
"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",
"IntegralFlowWithMultipliers" => {
"--arcs \"0>1,0>2,1>3,2>3\" --capacities 1,1,2,2 --source 0 --sink 3 --multipliers 1,2,3,1 --requirement 2"
}
"MinimumCutIntoBoundedSets" => {
"--graph 0-1,1-2,2-3 --edge-weights 1,1,1 --source 0 --sink 3 --size-bound 3 --cut-bound 1"
}
Expand Down Expand Up @@ -1116,6 +1121,95 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
)
}

// IntegralFlowWithMultipliers (directed arcs + capacities + source/sink + multipliers + requirement)
"IntegralFlowWithMultipliers" => {
let usage = "Usage: pred create IntegralFlowWithMultipliers --arcs \"0>1,0>2,1>3,2>3\" --capacities 1,1,2,2 --source 0 --sink 3 --multipliers 1,2,3,1 --requirement 2";
let arcs_str = args.arcs.as_deref().ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --arcs\n\n{usage}")
})?;
let (graph, num_arcs) = parse_directed_graph(arcs_str, args.num_vertices)
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
let capacities_str = args.capacities.as_deref().ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --capacities\n\n{usage}")
})?;
let capacities: Vec<u64> = util::parse_comma_list(capacities_str)
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
if capacities.len() != num_arcs {
bail!(
"Expected {} capacities but got {}\n\n{}",
num_arcs,
capacities.len(),
usage
);
}
for (arc_index, &capacity) in capacities.iter().enumerate() {
let fits = usize::try_from(capacity)
.ok()
.and_then(|value| value.checked_add(1))
.is_some();
if !fits {
bail!(
"capacity {} at arc index {} is too large for this platform\n\n{}",
capacity,
arc_index,
usage
);
}
}

let num_vertices = graph.num_vertices();
let source = args.source.ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --source\n\n{usage}")
})?;
let sink = args.sink.ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --sink\n\n{usage}")
})?;
validate_vertex_index("source", source, num_vertices, usage)?;
validate_vertex_index("sink", sink, num_vertices, usage)?;
if source == sink {
bail!(
"IntegralFlowWithMultipliers requires distinct --source and --sink\n\n{}",
usage
);
}

let multipliers_str = args.multipliers.as_deref().ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --multipliers\n\n{usage}")
})?;
let multipliers: Vec<u64> = util::parse_comma_list(multipliers_str)
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
if multipliers.len() != num_vertices {
bail!(
"Expected {} multipliers but got {}\n\n{}",
num_vertices,
multipliers.len(),
usage
);
}
if multipliers
.iter()
.enumerate()
.any(|(vertex, &multiplier)| vertex != source && vertex != sink && multiplier == 0)
{
bail!("non-terminal multipliers must be positive\n\n{usage}");
}

let requirement = args.requirement.ok_or_else(|| {
anyhow::anyhow!("IntegralFlowWithMultipliers requires --requirement\n\n{usage}")
})?;
(
ser(IntegralFlowWithMultipliers::new(
graph,
source,
sink,
multipliers,
capacities,
requirement,
))?,
resolved_variant.clone(),
)
}

// Minimum cut into bounded sets (graph + edge weights + s/t/B/K)
"MinimumCutIntoBoundedSets" => {
let (graph, _) = parse_graph(args).map_err(|e| {
Expand Down Expand Up @@ -5871,8 +5965,10 @@ mod tests {
edge_weights: None,
edge_lengths: None,
capacities: None,
multipliers: None,
source: None,
sink: None,
requirement: None,
num_paths_required: None,
couplings: None,
fields: None,
Expand Down
Loading
Loading