Skip to content

Commit da12028

Browse files
GiggleLiuzazabapclaude
authored
Fix #253: [Model] MultipleChoiceBranching (#656)
* Add plan for #253: [Model] MultipleChoiceBranching * Implement #253: [Model] MultipleChoiceBranching * chore: remove plan file after implementation * fix: address MultipleChoiceBranching review feedback * fix: harden MultipleChoiceBranching review follow-ups --------- Co-authored-by: zazabap <sweynan@icloud.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 47ea067 commit da12028

13 files changed

Lines changed: 1080 additions & 32 deletions

File tree

docs/paper/reductions.typ

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
"SubsetSum": [Subset Sum],
102102
"MinimumFeedbackArcSet": [Minimum Feedback Arc Set],
103103
"MinimumFeedbackVertexSet": [Minimum Feedback Vertex Set],
104+
"MultipleChoiceBranching": [Multiple Choice Branching],
104105
"ShortestCommonSupersequence": [Shortest Common Supersequence],
105106
"MinimumSumMulticenter": [Minimum Sum Multicenter],
106107
"SteinerTree": [Steiner Tree],
@@ -1945,6 +1946,46 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
19451946
*Example.* Consider $G$ with $V = {0, 1, 2, 3, 4, 5}$ and arcs $(0 arrow 1), (1 arrow 2), (2 arrow 0), (1 arrow 3), (3 arrow 4), (4 arrow 1), (2 arrow 5), (5 arrow 3), (3 arrow 0)$. This graph contains four directed cycles: $0 arrow 1 arrow 2 arrow 0$, $1 arrow 3 arrow 4 arrow 1$, $0 arrow 1 arrow 3 arrow 0$, and $2 arrow 5 arrow 3 arrow 0 arrow 1 arrow 2$. Removing $A' = {(0 arrow 1), (3 arrow 4)}$ breaks all four cycles (vertex 0 becomes a sink in the residual graph), giving a minimum FAS of size 2.
19461947
]
19471948

1949+
#{
1950+
let x = load-model-example("MultipleChoiceBranching")
1951+
let nv = graph-num-vertices(x.instance)
1952+
let arcs = x.instance.graph.inner.edges.map(e => (e.at(0), e.at(1)))
1953+
let sol = x.samples.at(0)
1954+
let chosen = sol.config.enumerate().filter(((i, v)) => v == 1).map(((i, _)) => i)
1955+
[
1956+
#problem-def("MultipleChoiceBranching")[
1957+
Given a directed graph $G = (V, A)$, arc weights $w: A -> ZZ^+$, a partition $A_1, A_2, dots, A_m$ of $A$, and a threshold $K in ZZ^+$, determine whether there exists a subset $A' subset.eq A$ with $sum_(a in A') w(a) >= K$ such that every vertex has in-degree at most one in $(V, A')$, the selected subgraph $(V, A')$ is acyclic, and $|A' inter A_i| <= 1$ for every partition group.
1958+
][
1959+
Multiple Choice Branching is the directed-graph problem ND11 in Garey & Johnson @garey1979. The partition constraint turns the polynomial-time maximum branching setting into an NP-complete decision problem: Garey and Johnson note that the problem remains NP-complete even when the digraph is strongly connected and all weights are equal, while the special case in which every partition group has size 1 reduces to ordinary maximum branching and becomes polynomial-time solvable @garey1979.
1960+
1961+
A conservative exact algorithm enumerates all $2^{|A|}$ arc subsets and checks the partition, in-degree, acyclicity, and threshold constraints in polynomial time. This is the brute-force search space used by the implementation.#footnote[We use the registry complexity bound $O^*(2^{|A|})$ for the full partitioned problem.]
1962+
1963+
*Example.* Consider the digraph on $n = #nv$ vertices with arcs $(0 arrow 1), (0 arrow 2), (1 arrow 3), (2 arrow 3), (1 arrow 4), (3 arrow 5), (4 arrow 5), (2 arrow 4)$, partition groups $A_1 = {(0 arrow 1), (0 arrow 2)}$, $A_2 = {(1 arrow 3), (2 arrow 3)}$, $A_3 = {(1 arrow 4), (2 arrow 4)}$, $A_4 = {(3 arrow 5), (4 arrow 5)}$, and threshold $K = 10$. The highlighted selection $A' = {(0 arrow 1), (1 arrow 3), (2 arrow 4), (3 arrow 5)}$ has total weight $3 + 4 + 3 + 3 = 13 >= 10$, uses exactly one arc from each partition group, and gives in-degrees 1 at vertices $1, 3, 4,$ and $5$. Because every selected arc points strictly left-to-right in the drawing, the selected subgraph is acyclic. The canonical fixture contains #x.optimal.len() satisfying selections for this instance; the figure highlights one of them.
1964+
1965+
#figure({
1966+
let verts = ((0, 1.6), (1.3, 2.3), (1.3, 0.9), (3.0, 2.3), (3.0, 0.9), (4.6, 1.6))
1967+
canvas(length: 1cm, {
1968+
for (idx, arc) in arcs.enumerate() {
1969+
let (u, v) = arc
1970+
let selected = chosen.contains(idx)
1971+
draw.line(
1972+
verts.at(u),
1973+
verts.at(v),
1974+
stroke: if selected { 2pt + graph-colors.at(0) } else { 0.9pt + luma(180) },
1975+
mark: (end: "straight", scale: if selected { 0.5 } else { 0.4 }),
1976+
)
1977+
}
1978+
for (k, pos) in verts.enumerate() {
1979+
g-node(pos, name: "v" + str(k), label: [$v_#k$])
1980+
}
1981+
})
1982+
},
1983+
caption: [Directed graph for Multiple Choice Branching. Blue arcs show the satisfying branching $(0 arrow 1), (1 arrow 3), (2 arrow 4), (3 arrow 5)$ of total weight 13; gray arcs are available but unselected.],
1984+
) <fig:mcb-example>
1985+
]
1986+
]
1987+
}
1988+
19481989
#problem-def("FlowShopScheduling")[
19491990
Given $m$ processors and a set $J$ of $n$ jobs, where each job $j in J$ consists of $m$ tasks $t_1 [j], t_2 [j], dots, t_m [j]$ with lengths $ell(t_i [j]) in ZZ^+_0$, and a deadline $D in ZZ^+$, determine whether there exists a permutation schedule $pi$ of the jobs such that all jobs complete by time $D$. Each job must be processed on machines $1, 2, dots, m$ in order, and job $j$ cannot start on machine $i+1$ until its task on machine $i$ is completed.
19501991
][

docs/src/reductions/problem_schemas.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,32 @@
575575
}
576576
]
577577
},
578+
{
579+
"name": "MultipleChoiceBranching",
580+
"description": "Find a branching with partition constraints and weight at least K",
581+
"fields": [
582+
{
583+
"name": "graph",
584+
"type_name": "DirectedGraph",
585+
"description": "The directed graph G=(V,A)"
586+
},
587+
{
588+
"name": "weights",
589+
"type_name": "Vec<W>",
590+
"description": "Arc weights w(a) for each arc a in A"
591+
},
592+
{
593+
"name": "partition",
594+
"type_name": "Vec<Vec<usize>>",
595+
"description": "Partition of arc indices; each arc index must appear in exactly one group"
596+
},
597+
{
598+
"name": "threshold",
599+
"type_name": "W::Sum",
600+
"description": "Weight threshold K"
601+
}
602+
]
603+
},
578604
{
579605
"name": "OptimalLinearArrangement",
580606
"description": "Find a vertex ordering on a line with total edge length at most K",

docs/src/reductions/reduction_graph.json

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,15 @@
429429
"doc_path": "models/graph/struct.MinimumVertexCover.html",
430430
"complexity": "1.1996^num_vertices"
431431
},
432+
{
433+
"name": "MultipleChoiceBranching",
434+
"variant": {
435+
"weight": "i32"
436+
},
437+
"category": "graph",
438+
"doc_path": "models/graph/struct.MultipleChoiceBranching.html",
439+
"complexity": "2^num_arcs"
440+
},
432441
{
433442
"name": "OptimalLinearArrangement",
434443
"variant": {
@@ -599,7 +608,7 @@
599608
},
600609
{
601610
"source": 4,
602-
"target": 57,
611+
"target": 58,
603612
"overhead": [
604613
{
605614
"field": "num_spins",
@@ -659,7 +668,7 @@
659668
},
660669
{
661670
"source": 13,
662-
"target": 51,
671+
"target": 52,
663672
"overhead": [
664673
{
665674
"field": "num_vars",
@@ -700,7 +709,7 @@
700709
},
701710
{
702711
"source": 20,
703-
"target": 51,
712+
"target": 52,
704713
"overhead": [
705714
{
706715
"field": "num_vars",
@@ -726,7 +735,7 @@
726735
},
727736
{
728737
"source": 21,
729-
"target": 51,
738+
"target": 52,
730739
"overhead": [
731740
{
732741
"field": "num_vars",
@@ -752,7 +761,7 @@
752761
},
753762
{
754763
"source": 22,
755-
"target": 51,
764+
"target": 52,
756765
"overhead": [
757766
{
758767
"field": "num_vars",
@@ -763,7 +772,7 @@
763772
},
764773
{
765774
"source": 22,
766-
"target": 61,
775+
"target": 62,
767776
"overhead": [
768777
{
769778
"field": "num_elements",
@@ -774,7 +783,7 @@
774783
},
775784
{
776785
"source": 23,
777-
"target": 53,
786+
"target": 54,
778787
"overhead": [
779788
{
780789
"field": "num_clauses",
@@ -793,7 +802,7 @@
793802
},
794803
{
795804
"source": 24,
796-
"target": 51,
805+
"target": 52,
797806
"overhead": [
798807
{
799808
"field": "num_vars",
@@ -819,7 +828,7 @@
819828
},
820829
{
821830
"source": 27,
822-
"target": 57,
831+
"target": 58,
823832
"overhead": [
824833
{
825834
"field": "num_spins",
@@ -1134,7 +1143,7 @@
11341143
},
11351144
{
11361145
"source": 39,
1137-
"target": 51,
1146+
"target": 52,
11381147
"overhead": [
11391148
{
11401149
"field": "num_vars",
@@ -1249,7 +1258,7 @@
12491258
"doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html"
12501259
},
12511260
{
1252-
"source": 51,
1261+
"source": 52,
12531262
"target": 13,
12541263
"overhead": [
12551264
{
@@ -1264,8 +1273,8 @@
12641273
"doc_path": "rules/qubo_ilp/index.html"
12651274
},
12661275
{
1267-
"source": 51,
1268-
"target": 56,
1276+
"source": 52,
1277+
"target": 57,
12691278
"overhead": [
12701279
{
12711280
"field": "num_spins",
@@ -1275,7 +1284,7 @@
12751284
"doc_path": "rules/spinglass_qubo/index.html"
12761285
},
12771286
{
1278-
"source": 53,
1287+
"source": 54,
12791288
"target": 4,
12801289
"overhead": [
12811290
{
@@ -1290,7 +1299,7 @@
12901299
"doc_path": "rules/sat_circuitsat/index.html"
12911300
},
12921301
{
1293-
"source": 53,
1302+
"source": 54,
12941303
"target": 17,
12951304
"overhead": [
12961305
{
@@ -1305,7 +1314,7 @@
13051314
"doc_path": "rules/sat_coloring/index.html"
13061315
},
13071316
{
1308-
"source": 53,
1317+
"source": 54,
13091318
"target": 22,
13101319
"overhead": [
13111320
{
@@ -1320,7 +1329,7 @@
13201329
"doc_path": "rules/sat_ksat/index.html"
13211330
},
13221331
{
1323-
"source": 53,
1332+
"source": 54,
13241333
"target": 32,
13251334
"overhead": [
13261335
{
@@ -1335,7 +1344,7 @@
13351344
"doc_path": "rules/sat_maximumindependentset/index.html"
13361345
},
13371346
{
1338-
"source": 53,
1347+
"source": 54,
13391348
"target": 41,
13401349
"overhead": [
13411350
{
@@ -1350,8 +1359,8 @@
13501359
"doc_path": "rules/sat_minimumdominatingset/index.html"
13511360
},
13521361
{
1353-
"source": 56,
1354-
"target": 51,
1362+
"source": 57,
1363+
"target": 52,
13551364
"overhead": [
13561365
{
13571366
"field": "num_vars",
@@ -1361,7 +1370,7 @@
13611370
"doc_path": "rules/spinglass_qubo/index.html"
13621371
},
13631372
{
1364-
"source": 57,
1373+
"source": 58,
13651374
"target": 27,
13661375
"overhead": [
13671376
{
@@ -1376,8 +1385,8 @@
13761385
"doc_path": "rules/spinglass_maxcut/index.html"
13771386
},
13781387
{
1379-
"source": 57,
1380-
"target": 56,
1388+
"source": 58,
1389+
"target": 57,
13811390
"overhead": [
13821391
{
13831392
"field": "num_spins",
@@ -1391,7 +1400,7 @@
13911400
"doc_path": "rules/spinglass_casts/index.html"
13921401
},
13931402
{
1394-
"source": 62,
1403+
"source": 63,
13951404
"target": 13,
13961405
"overhead": [
13971406
{
@@ -1406,8 +1415,8 @@
14061415
"doc_path": "rules/travelingsalesman_ilp/index.html"
14071416
},
14081417
{
1409-
"source": 62,
1410-
"target": 51,
1418+
"source": 63,
1419+
"target": 52,
14111420
"overhead": [
14121421
{
14131422
"field": "num_vars",

problemreductions-cli/src/cli.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ Flags by problem type:
240240
CVP --basis, --target-vec [--bounds]
241241
OptimalLinearArrangement --graph, --bound
242242
RuralPostman (RPP) --graph, --edge-weights, --required-edges, --bound
243+
MultipleChoiceBranching --arcs [--weights] --partition --bound [--num-vertices]
243244
SubgraphIsomorphism --graph (host), --pattern (pattern)
244245
LCS --strings
245246
FAS --arcs [--weights] [--num-vertices]
@@ -264,6 +265,7 @@ Examples:
264265
pred create MIS --graph 0-1,1-2,2-3 --weights 1,1,1
265266
pred create SAT --num-vars 3 --clauses \"1,2;-1,3\"
266267
pred create QUBO --matrix \"1,0.5;0.5,2\"
268+
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
267269
pred create MIS/KingsSubgraph --positions \"0,0;1,0;1,1;0,1\"
268270
pred create MIS/UnitDiskGraph --positions \"0,0;1,0;0.5,0.8\" --radius 1.5
269271
pred create MIS --random --num-vertices 10 --edge-prob 0.3
@@ -380,6 +382,9 @@ pub struct CreateArgs {
380382
/// Sets for SetPacking/SetCovering (semicolon-separated, e.g., "0,1;1,2;0,2")
381383
#[arg(long)]
382384
pub sets: Option<String>,
385+
/// Partition groups for arc-index partitions (semicolon-separated, e.g., "0,1;2,3")
386+
#[arg(long)]
387+
pub partition: Option<String>,
383388
/// Universe size for MinimumSetCovering
384389
#[arg(long)]
385390
pub universe: Option<usize>,
@@ -413,7 +418,7 @@ pub struct CreateArgs {
413418
/// Required edge indices for RuralPostman (comma-separated, e.g., "0,2,4")
414419
#[arg(long)]
415420
pub required_edges: Option<String>,
416-
/// Upper bound or length bound (for LengthBoundedDisjointPaths, OptimalLinearArrangement, RuralPostman, or SCS)
421+
/// Upper bound or length bound (for LengthBoundedDisjointPaths, MultipleChoiceBranching, OptimalLinearArrangement, RuralPostman, or SCS)
417422
#[arg(long, allow_hyphen_values = true)]
418423
pub bound: Option<i64>,
419424
/// Pattern graph edge list for SubgraphIsomorphism (e.g., 0-1,1-2,2-0)

0 commit comments

Comments
 (0)