Skip to content

Commit 638c7f7

Browse files
GiggleLiuclaude
andauthored
Fix #290: [Model] IntegralFlowWithMultipliers (#736)
* Add plan for #290: [Model] IntegralFlowWithMultipliers * Add IntegralFlowWithMultipliers model * Polish IntegralFlowWithMultipliers paper and tests * chore: remove plan file after implementation * Fix formatting after merge conflict resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix paper solve command: add --solver brute-force (no ILP path for this model) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 29ae20f commit 638c7f7

10 files changed

Lines changed: 814 additions & 12 deletions

File tree

docs/paper/reductions.typ

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"ConsecutiveBlockMinimization": [Consecutive Block Minimization],
132132
"ConsecutiveOnesSubmatrix": [Consecutive Ones Submatrix],
133133
"DirectedTwoCommodityIntegralFlow": [Directed Two-Commodity Integral Flow],
134+
"IntegralFlowWithMultipliers": [Integral Flow With Multipliers],
134135
"MinMaxMulticenter": [Min-Max Multicenter],
135136
"FlowShopScheduling": [Flow Shop Scheduling],
136137
"MinimumCutIntoBoundedSets": [Minimum Cut Into Bounded Sets],
@@ -5251,6 +5252,71 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
52515252
) <fig:d2cif>
52525253
]
52535254

5255+
#{
5256+
let x = load-model-example("IntegralFlowWithMultipliers")
5257+
let config = x.optimal_config
5258+
[
5259+
#problem-def("IntegralFlowWithMultipliers")[
5260+
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$.
5261+
][
5262+
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.
5263+
5264+
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)$.
5265+
5266+
*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$.
5267+
5268+
#pred-commands(
5269+
"pred create --example IntegralFlowWithMultipliers -o integral-flow-with-multipliers.json",
5270+
"pred solve integral-flow-with-multipliers.json --solver brute-force",
5271+
"pred evaluate integral-flow-with-multipliers.json --config " + config.map(str).join(","),
5272+
)
5273+
5274+
#figure(
5275+
canvas(length: 0.9cm, {
5276+
import draw: *
5277+
let blue = graph-colors.at(0)
5278+
let gray = luma(180)
5279+
let source = (0, 0)
5280+
let sink = (6, 0)
5281+
let mids = (
5282+
(2.4, 2.5),
5283+
(2.4, 1.5),
5284+
(2.4, 0.5),
5285+
(2.4, -0.5),
5286+
(2.4, -1.5),
5287+
(2.4, -2.5),
5288+
)
5289+
let labels = (
5290+
[$v_1, h = 2$],
5291+
[$v_2, h = 3$],
5292+
[$v_3, h = 4$],
5293+
[$v_4, h = 5$],
5294+
[$v_5, h = 6$],
5295+
[$v_6, h = 4$],
5296+
)
5297+
let active = (0, 2, 4)
5298+
5299+
for (i, pos) in mids.enumerate() {
5300+
let chosen = active.contains(i)
5301+
let color = if chosen { blue } else { gray }
5302+
let thickness = if chosen { 1.3pt } else { 0.6pt }
5303+
line(source, pos, stroke: (paint: color, thickness: thickness), mark: (end: "straight", scale: 0.45))
5304+
line(pos, sink, stroke: (paint: color, thickness: thickness), mark: (end: "straight", scale: 0.45))
5305+
circle(pos, radius: 0.22, fill: if chosen { blue.lighten(75%) } else { white }, stroke: 0.6pt)
5306+
content((pos.at(0) + 0.85, pos.at(1)), text(6.5pt, labels.at(i)))
5307+
}
5308+
5309+
circle(source, radius: 0.24, fill: blue.lighten(75%), stroke: 0.6pt)
5310+
circle(sink, radius: 0.24, fill: blue.lighten(75%), stroke: 0.6pt)
5311+
content(source, text(7pt, [$s = v_0$]))
5312+
content(sink, text(7pt, [$t = v_7$]))
5313+
}),
5314+
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$.],
5315+
) <fig:ifwm>
5316+
]
5317+
]
5318+
}
5319+
52545320
#problem-def("AdditionalKey")[
52555321
Given a set $A$ of attribute names, a collection $F$ of functional dependencies on $A$,
52565322
a subset $R subset.eq A$, and a set $K$ of candidate keys for the relational scheme $chevron.l R, F chevron.r$,

docs/paper/references.bib

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,27 @@ @article{evenItaiShamir1976
255255
doi = {10.1137/0205048}
256256
}
257257

258+
@article{sahni1974,
259+
author = {Sartaj Sahni},
260+
title = {Computationally Related Problems},
261+
journal = {SIAM Journal on Computing},
262+
volume = {3},
263+
number = {4},
264+
pages = {262--279},
265+
year = {1974}
266+
}
267+
268+
@article{jewell1962,
269+
author = {William S. Jewell},
270+
title = {Optimal Flow Through Networks with Gains},
271+
journal = {Operations Research},
272+
volume = {10},
273+
number = {4},
274+
pages = {476--499},
275+
year = {1962},
276+
doi = {10.1287/opre.10.4.476}
277+
}
278+
258279
@article{abdelWahabKameda1978,
259280
author = {H. M. Abdel-Wahab and T. Kameda},
260281
title = {Scheduling to Minimize Maximum Cumulative Cost Subject to Series-Parallel Precedence Constraints},

problemreductions-cli/src/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ Flags by problem type:
229229
PartitionIntoTriangles --graph
230230
GraphPartitioning --graph
231231
GeneralizedHex --graph, --source, --sink
232+
IntegralFlowWithMultipliers --arcs, --capacities, --source, --sink, --multipliers, --requirement
232233
MinimumCutIntoBoundedSets --graph, --edge-weights, --source, --sink, --size-bound, --cut-bound
233234
HamiltonianCircuit, HC --graph
234235
LongestCircuit --graph, --edge-weights, --bound
@@ -313,6 +314,7 @@ Examples:
313314
pred create SAT --num-vars 3 --clauses \"1,2;-1,3\"
314315
pred create QUBO --matrix \"1,0.5;0.5,2\"
315316
pred create GeneralizedHex --graph 0-1,0-2,0-3,1-4,2-4,3-4,4-5 --source 0 --sink 5
317+
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
316318
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
317319
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
318320
pred create MIS/KingsSubgraph --positions \"0,0;1,0;1,1;0,1\"
@@ -356,12 +358,18 @@ pub struct CreateArgs {
356358
/// Edge capacities for multicommodity flow problems (e.g., 1,1,2)
357359
#[arg(long)]
358360
pub capacities: Option<String>,
361+
/// Vertex multipliers in vertex order (e.g., 1,2,3,1)
362+
#[arg(long)]
363+
pub multipliers: Option<String>,
359364
/// Source vertex for path-based graph problems and MinimumCutIntoBoundedSets
360365
#[arg(long)]
361366
pub source: Option<usize>,
362367
/// Sink vertex for path-based graph problems and MinimumCutIntoBoundedSets
363368
#[arg(long)]
364369
pub sink: Option<usize>,
370+
/// Required sink inflow for IntegralFlowWithMultipliers
371+
#[arg(long)]
372+
pub requirement: Option<u64>,
365373
/// Required number of paths for LengthBoundedDisjointPaths
366374
#[arg(long)]
367375
pub num_paths_required: Option<usize>,

problemreductions-cli/src/commands/create.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ fn all_data_flags_empty(args: &CreateArgs) -> bool {
5050
&& args.edge_weights.is_none()
5151
&& args.edge_lengths.is_none()
5252
&& args.capacities.is_none()
53+
&& args.multipliers.is_none()
5354
&& args.source.is_none()
5455
&& args.sink.is_none()
56+
&& args.requirement.is_none()
5557
&& args.num_paths_required.is_none()
5658
&& args.couplings.is_none()
5759
&& args.fields.is_none()
@@ -513,6 +515,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
513515
"KClique" => "--graph 0-1,0-2,1-3,2-3,2-4,3-4 --k 3",
514516
"GraphPartitioning" => "--graph 0-1,1-2,2-3,0-2,1-3,0-3",
515517
"GeneralizedHex" => "--graph 0-1,0-2,0-3,1-4,2-4,3-4,4-5 --source 0 --sink 5",
518+
"IntegralFlowWithMultipliers" => {
519+
"--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"
520+
}
516521
"MinimumCutIntoBoundedSets" => {
517522
"--graph 0-1,1-2,2-3 --edge-weights 1,1,1 --source 0 --sink 3 --size-bound 3 --cut-bound 1"
518523
}
@@ -1116,6 +1121,95 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
11161121
)
11171122
}
11181123

1124+
// IntegralFlowWithMultipliers (directed arcs + capacities + source/sink + multipliers + requirement)
1125+
"IntegralFlowWithMultipliers" => {
1126+
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";
1127+
let arcs_str = args.arcs.as_deref().ok_or_else(|| {
1128+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --arcs\n\n{usage}")
1129+
})?;
1130+
let (graph, num_arcs) = parse_directed_graph(arcs_str, args.num_vertices)
1131+
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
1132+
let capacities_str = args.capacities.as_deref().ok_or_else(|| {
1133+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --capacities\n\n{usage}")
1134+
})?;
1135+
let capacities: Vec<u64> = util::parse_comma_list(capacities_str)
1136+
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
1137+
if capacities.len() != num_arcs {
1138+
bail!(
1139+
"Expected {} capacities but got {}\n\n{}",
1140+
num_arcs,
1141+
capacities.len(),
1142+
usage
1143+
);
1144+
}
1145+
for (arc_index, &capacity) in capacities.iter().enumerate() {
1146+
let fits = usize::try_from(capacity)
1147+
.ok()
1148+
.and_then(|value| value.checked_add(1))
1149+
.is_some();
1150+
if !fits {
1151+
bail!(
1152+
"capacity {} at arc index {} is too large for this platform\n\n{}",
1153+
capacity,
1154+
arc_index,
1155+
usage
1156+
);
1157+
}
1158+
}
1159+
1160+
let num_vertices = graph.num_vertices();
1161+
let source = args.source.ok_or_else(|| {
1162+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --source\n\n{usage}")
1163+
})?;
1164+
let sink = args.sink.ok_or_else(|| {
1165+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --sink\n\n{usage}")
1166+
})?;
1167+
validate_vertex_index("source", source, num_vertices, usage)?;
1168+
validate_vertex_index("sink", sink, num_vertices, usage)?;
1169+
if source == sink {
1170+
bail!(
1171+
"IntegralFlowWithMultipliers requires distinct --source and --sink\n\n{}",
1172+
usage
1173+
);
1174+
}
1175+
1176+
let multipliers_str = args.multipliers.as_deref().ok_or_else(|| {
1177+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --multipliers\n\n{usage}")
1178+
})?;
1179+
let multipliers: Vec<u64> = util::parse_comma_list(multipliers_str)
1180+
.map_err(|e| anyhow::anyhow!("{e}\n\n{usage}"))?;
1181+
if multipliers.len() != num_vertices {
1182+
bail!(
1183+
"Expected {} multipliers but got {}\n\n{}",
1184+
num_vertices,
1185+
multipliers.len(),
1186+
usage
1187+
);
1188+
}
1189+
if multipliers
1190+
.iter()
1191+
.enumerate()
1192+
.any(|(vertex, &multiplier)| vertex != source && vertex != sink && multiplier == 0)
1193+
{
1194+
bail!("non-terminal multipliers must be positive\n\n{usage}");
1195+
}
1196+
1197+
let requirement = args.requirement.ok_or_else(|| {
1198+
anyhow::anyhow!("IntegralFlowWithMultipliers requires --requirement\n\n{usage}")
1199+
})?;
1200+
(
1201+
ser(IntegralFlowWithMultipliers::new(
1202+
graph,
1203+
source,
1204+
sink,
1205+
multipliers,
1206+
capacities,
1207+
requirement,
1208+
))?,
1209+
resolved_variant.clone(),
1210+
)
1211+
}
1212+
11191213
// Minimum cut into bounded sets (graph + edge weights + s/t/B/K)
11201214
"MinimumCutIntoBoundedSets" => {
11211215
let (graph, _) = parse_graph(args).map_err(|e| {
@@ -5871,8 +5965,10 @@ mod tests {
58715965
edge_weights: None,
58725966
edge_lengths: None,
58735967
capacities: None,
5968+
multipliers: None,
58745969
source: None,
58755970
sink: None,
5971+
requirement: None,
58765972
num_paths_required: None,
58775973
couplings: None,
58785974
fields: None,

0 commit comments

Comments
 (0)