-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathreductions.typ
More file actions
1735 lines (1441 loc) · 125 KB
/
Copy pathreductions.typ
File metadata and controls
1735 lines (1441 loc) · 125 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Problem Reductions: A Mathematical Reference
#let graph-data = json("../src/reductions/reduction_graph.json")
#import "@preview/cetz:0.4.2": canvas, draw
#import "@preview/ctheorems:1.1.3": thmbox, thmplain, thmproof, thmrules
#import "lib.typ": g-node, g-edge, petersen-graph, house-graph, octahedral-graph, draw-grid-graph, draw-triangular-graph, graph-colors, selem, sregion, draw-node-highlight, draw-edge-highlight, draw-node-colors, sregion-selected, sregion-dimmed, gate-and, gate-or, gate-xor
#set page(paper: "a4", margin: (x: 2cm, y: 2.5cm))
#set text(font: "New Computer Modern", size: 10pt)
#set par(justify: true)
#set heading(numbering: "1.1")
#show link: set text(blue)
// Set up theorem environments with ctheorems
#show: thmrules.with(qed-symbol: $square$)
// === Example JSON helpers ===
// Load example JSON files generated by `make examples`.
// Unified schema: { source: { problem, variant, instance }, target: { ... }, overhead: [...] }
#let load-example(name) = json("examples/" + name + ".json")
// Load result JSON: { solutions: [{ source_config, target_config }, ...] }
#let load-results(name) = json("examples/" + name + ".result.json")
#let problem-schemas = json("../src/reductions/problem_schemas.json")
// Problem display names for theorem headers
#let display-name = (
"MaximumIndependentSet": [Maximum Independent Set],
"MinimumVertexCover": [Minimum Vertex Cover],
"MaxCut": [Max-Cut],
"KColoring": [$k$-Coloring],
"MinimumDominatingSet": [Minimum Dominating Set],
"MaximumMatching": [Maximum Matching],
"TravelingSalesman": [Traveling Salesman],
"MaximumClique": [Maximum Clique],
"MaximumSetPacking": [Maximum Set Packing],
"MinimumSetCovering": [Minimum Set Covering],
"SpinGlass": [Spin Glass],
"QUBO": [QUBO],
"ILP": [Integer Linear Programming],
"Satisfiability": [SAT],
"KSatisfiability": [$k$-SAT],
"CircuitSAT": [CircuitSAT],
"Factoring": [Factoring],
"KingsSubgraph": [King's Subgraph MIS],
"TriangularSubgraph": [Triangular Subgraph MIS],
"MaximalIS": [Maximal Independent Set],
"BMF": [Boolean Matrix Factorization],
"PaintShop": [Paint Shop],
"BicliqueCover": [Biclique Cover],
"BinPacking": [Bin Packing],
"ClosestVectorProblem": [Closest Vector Problem],
)
// Definition label: "def:<ProblemName>" — each definition block must have a matching label
// Generate theorem label from source/target names (uses full names for consistency)
#let reduction-label(source, target) = {
label("thm:" + source + "-to-" + target)
}
// State for tracking which reduction rules are described in the paper
#let covered-rules = state("covered-rules", ())
// Extract reductions for a problem from graph-data (returns (name, label) pairs)
#let get-reductions-to(problem-name) = {
graph-data.edges
.filter(e => graph-data.nodes.at(e.source).name == problem-name)
.map(e => (name: graph-data.nodes.at(e.target).name, lbl: reduction-label(graph-data.nodes.at(e.source).name, graph-data.nodes.at(e.target).name)))
.dedup(key: e => e.name)
}
#let get-reductions-from(problem-name) = {
graph-data.edges
.filter(e => graph-data.nodes.at(e.target).name == problem-name)
.map(e => (name: graph-data.nodes.at(e.source).name, lbl: reduction-label(graph-data.nodes.at(e.source).name, graph-data.nodes.at(e.target).name)))
.dedup(key: e => e.name)
}
// Render a single reduction with link (uses context to skip broken links gracefully)
#let render-reduction-link(r) = {
context {
if query(r.lbl).len() > 0 { link(r.lbl)[#r.name] }
else { r.name }
}
}
// Render complexity from graph-data nodes
#let render-complexity(name) = {
let nodes = graph-data.nodes.filter(n => n.name == name)
if nodes.len() == 0 { return }
let seen = ()
let entries = ()
for node in nodes {
if node.complexity not in seen {
seen.push(node.complexity)
entries.push(node.complexity)
}
}
block(above: 0.5em)[
#set text(size: 9pt)
- Complexity: #entries.map(e => raw(e)).join("; ").
]
}
// Render the "Reduces to/from" lines for a problem
#let render-reductions(problem-name) = {
let reduces-to = get-reductions-to(problem-name)
let reduces-from = get-reductions-from(problem-name)
if reduces-to.len() > 0 or reduces-from.len() > 0 {
block(above: 0.5em)[
#set text(size: 9pt)
#if reduces-to.len() > 0 [
- Reduces to: #reduces-to.map(render-reduction-link).join(", "). \
]
#if reduces-from.len() > 0 [
- Reduces from: #reduces-from.map(render-reduction-link).join(", ").
]
]
}
}
// Render a problem's JSON schema as a field table (subtle styling)
#let render-schema(name) = {
let schema = problem-schemas.find(s => s.name == name)
if schema == none { return }
block(
stroke: (left: 2pt + luma(180)),
inset: (left: 8pt),
)[
#set text(size: 9pt)
#table(
columns: (auto, 1fr),
inset: (x: 2pt, y: 3pt),
align: (left, left),
stroke: none,
table.header(
text(fill: luma(30), raw(name)),
),
table.hline(stroke: 0.3pt + luma(200)),
..schema.fields.map(f => (
text(fill: luma(60), raw(f.name)),
text(fill: luma(60), raw(f.description))
)).flatten()
)
]
}
// Render a concrete example box from JSON data (unified schema)
#let reduction-example(data, caption: none, body) = {
block(
width: 100%,
inset: (x: 1em, y: 0.8em),
fill: rgb("#f0f7ff"),
stroke: (left: 2pt + rgb("#4a86e8")),
)[
#if caption != none {
text(weight: "bold")[Example: #caption]
parbreak()
}
*Source:* #data.source.problem
#h(1em)
*Target:* #data.target.problem
#if body != none { parbreak(); body }
]
}
#let theorem = thmplain("theorem", [#h(-1.2em)Rule], base_level: 1)
#let proof = thmproof("proof", "Proof")
#let definition = thmbox(
"definition",
"Definition",
fill: rgb("#f8f8f8"),
stroke: (left: 2pt + rgb("#4a86e8")),
inset: (x: 1em, y: 0.8em),
breakable: true,
base_level: 1,
)
// Problem definition wrapper: auto-adds schema, complexity, reductions list, and label
#let problem-def(name, def, body) = {
let lbl = label("def:" + name)
let title = display-name.at(name)
[#definition(title)[
#def
#render-complexity(name)
#render-reductions(name)
#render-schema(name)
#body
]
#lbl]
}
// Find edge in graph-data by source/target names
#let find-edge(source, target) = {
let edge = graph-data.edges.find(e => graph-data.nodes.at(e.source).name == source and graph-data.nodes.at(e.target).name == target)
if edge == none {
edge = graph-data.edges.find(e => graph-data.nodes.at(e.source).name == target and graph-data.nodes.at(e.target).name == source)
}
edge
}
// Build display name from a graph-data node (name + variant)
#let variant-display(node) = {
let base = display-name.at(node.name)
if node.variant.len() == 0 { return base }
let parts = ()
if "graph" in node.variant and node.variant.graph != "SimpleGraph" {
parts.push(node.variant.graph)
}
if "weight" in node.variant {
if node.variant.weight == "i32" { parts.push("weighted") }
else if node.variant.weight == "f64" { parts.push("real-weighted") }
}
if "k" in node.variant { parts.push[$k$-ary] }
if parts.len() > 0 { [#base (#parts.join(", "))] } else { base }
}
// Format overhead fields as inline text
#let format-overhead(overhead) = {
let parts = overhead.map(o => raw(o.field + " = " + o.formula))
[_Overhead:_ #parts.join(", ").]
}
// Unified function for reduction rules: theorem + proof + optional example
#let reduction-rule(
source, target,
example: false,
example-caption: none,
extra: none,
theorem-body, proof-body,
) = {
let arrow = sym.arrow.r
let edge = find-edge(source, target)
let src-disp = if edge != none { variant-display(graph-data.nodes.at(edge.source)) }
else { display-name.at(source) }
let tgt-disp = if edge != none { variant-display(graph-data.nodes.at(edge.target)) }
else { display-name.at(target) }
let src-lbl = label("def:" + source)
let tgt-lbl = label("def:" + target)
let overhead = if edge != none and edge.overhead.len() > 0 { edge.overhead } else { none }
let thm-lbl = label("thm:" + source + "-to-" + target)
// Derive example filename from source/target: "Source" → "source", then "source_to_target"
let example-name = lower(source) + "_to_" + lower(target)
covered-rules.update(old => old + ((source, target),))
[
#v(1em)
#theorem[
*(*#context { if query(src-lbl).len() > 0 { link(src-lbl)[#src-disp] } else [#src-disp] }* #arrow *#context { if query(tgt-lbl).len() > 0 { link(tgt-lbl)[#tgt-disp] } else [#tgt-disp] }*)* #theorem-body
#if overhead != none { linebreak(); format-overhead(overhead) }
] #thm-lbl]
proof[#proof-body]
if example {
let data = load-example(example-name)
pad(left: 1.5em, reduction-example(data, caption: example-caption)[#extra])
}
}
#align(center)[
#text(size: 16pt, weight: "bold")[Problem Reductions: Models and Transformations]
#v(0.5em)
#text(size: 11pt)[Jin-Guo Liu#super[1] #h(1em) Xi-Wei Pan#super[1]]
#v(0.3em)
#text(size: 9pt)[#super[1]Hong Kong University of Science and Technology (Guangzhou)]
#v(0.3em)
#text(size: 10pt, style: "italic")[github.com/CodingThrust/problem-reductions]
#v(1em)
]
#block(width: 100%, inset: (x: 2em, y: 1em))[
*Abstract.* We present formal definitions for computational problems and polynomial-time reductions implemented in the `problem-reductions` library. For each reduction, we state theorems with constructive proofs that preserve solution structure.
]
// Table of contents
#outline(title: "Contents", indent: 1.5em, depth: 2)
#pagebreak()
= Introduction
A _reduction_ from problem $A$ to problem $B$, denoted $A arrow.long B$, is a polynomial-time transformation of $A$-instances into $B$-instances such that: (1) the transformation runs in polynomial time, (2) solutions to $B$ can be efficiently mapped back to solutions of $A$, and (3) optimal solutions are preserved. The library implements #graph-data.edges.len() reductions connecting #graph-data.nodes.len() problem types.
== Notation
We use the following notation throughout. An _undirected graph_ $G = (V, E)$ consists of a vertex set $V$ and edge set $E subset.eq binom(V, 2)$. For a set $S$, $overline(S)$ or $V backslash S$ denotes its complement. We write $|S|$ for cardinality. A _clique_ in $G$ is a subset $K subset.eq V$ where every pair of distinct vertices is adjacent: $(u, v) in E$ for all distinct $u, v in K$. A _unit disk graph_ is a graph where vertices are points on a 2D lattice and $(u, v) in E$ iff $d(u, v) <= r$ for some radius $r$; a _King's subgraph_ uses the 8-connectivity square grid with $r approx 1.5$. For Boolean variables, $overline(x)$ denotes negation ($not x$). A _literal_ is a variable $x$ or its negation $overline(x)$. A _clause_ is a disjunction of literals. A formula in _conjunctive normal form_ (CNF) is a conjunction of clauses. We abbreviate Independent Set as IS, Vertex Cover as VC, and use $n$ for problem size, $m$ for number of clauses, and $k_j = |C_j|$ for clause size.
= Problem Definitions <sec:problems>
Each problem definition follows this structure:
#block(
inset: (x: 1em, y: 0.8em),
fill: rgb("#f8f8f8"),
stroke: (left: 2pt + rgb("#4a86e8")),
)[
*Definition N (Problem Name).* Formal problem statement defining input, constraints, and objective.
#block(
stroke: (left: 2pt + luma(180)),
inset: (left: 8pt),
)[
#set text(size: 9pt)
#table(
columns: (auto, 1fr),
inset: (x: 6pt, y: 3pt),
align: (left, left),
stroke: none,
table.header(text(fill: luma(30), raw("ProblemName"))),
table.hline(stroke: 0.3pt + luma(200)),
text(fill: luma(60), raw("field_name")), text(fill: luma(60), raw("Field description from JSON schema")),
)
]
#set text(size: 9pt, fill: luma(60))
_Reduces to:_ ProblemA, ProblemB. \
_Reduces from:_ ProblemC.
]
The gray schema table shows the JSON field names used in the library's data structures. The reduction links at the bottom connect to the corresponding theorems in @sec:reductions.
== Graph Problems
In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| = n$ vertices and $|E|$ edges.
#problem-def("MaximumIndependentSet")[
Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ maximizing $sum_(v in S) w(v)$ such that no two vertices in $S$ are adjacent: $forall u, v in S: (u, v) in.not E$.
][
One of Karp's 21 NP-complete problems @karp1972, MIS appears in wireless network scheduling, register allocation, and coding theory @shannon1956. Solvable in polynomial time on bipartite graphs (König's theorem), interval graphs, chordal graphs, and cographs. The best known algorithm runs in $O^*(1.1996^n)$ time via measure-and-conquer branching @xiao2017.
*Example.* Consider the Petersen graph $G$ with $n = 10$ vertices, $|E| = 15$ edges, and unit weights $w(v) = 1$ for all $v in V$. The graph is 3-regular (every vertex has degree 3). A maximum independent set is $S = {v_1, v_3, v_5, v_9}$ with $w(S) = sum_(v in S) w(v) = 4 = alpha(G)$. No two vertices in $S$ share an edge, and no vertex can be added without violating independence.
#figure({
let pg = petersen-graph()
draw-node-highlight(pg.vertices, pg.edges, (1, 3, 5, 9))
},
caption: [The Petersen graph with a maximum independent set $S = {v_1, v_3, v_5, v_9}$ shown in blue ($alpha(G) = 4$). Outer vertices $v_0, ..., v_4$ form a pentagon; inner vertices $v_5, ..., v_9$ form a pentagram. Unit weights $w(v_i) = 1$.],
) <fig:petersen-mis>
]
#problem-def("MinimumVertexCover")[
Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ minimizing $sum_(v in S) w(v)$ such that every edge has at least one endpoint in $S$: $forall (u, v) in E: u in S or v in S$.
][
One of Karp's 21 NP-complete problems @karp1972. Vertex Cover is the complement of Independent Set: $S$ is a vertex cover iff $V backslash S$ is an independent set, so $|"VC"| + |"IS"| = n$. Central to parameterized complexity, admitting FPT algorithms in $O^*(1.2738^k)$ time parameterized by solution size $k$. The best known exact algorithm runs in $O^*(1.1996^n)$ via the MIS complement @xiao2017.
*Example.* Consider the house graph $G$ with $n = 5$ vertices, $|E| = 6$ edges, and unit weights $w(v) = 1$. A minimum vertex cover is $S = {v_0, v_3, v_4}$ with $w(S) = 3$: edge $(v_0, v_1)$ is covered by $v_0$; $(v_0, v_2)$ by $v_0$; $(v_1, v_3)$ by $v_3$; $(v_2, v_3)$ by $v_3$; $(v_2, v_4)$ by $v_4$; $(v_3, v_4)$ by both. The complement ${v_1, v_2}$ is a maximum independent set ($alpha(G) = 2$, confirming $|"VC"| = n - alpha = 3$).
#figure({
let hg = house-graph()
draw-node-highlight(hg.vertices, hg.edges, (0, 3, 4))
},
caption: [The house graph with a minimum vertex cover $S = {v_0, v_3, v_4}$ shown in blue ($w(S) = 3$). Every edge is incident to at least one blue vertex.],
) <fig:house-vc>
]
#problem-def("MaxCut")[
Given $G = (V, E)$ with weights $w: E -> RR$, find partition $(S, overline(S))$ maximizing $sum_((u,v) in E: u in S, v in overline(S)) w(u, v)$.
][
Max-Cut is NP-hard on general graphs @barahona1982 but polynomial-time solvable on planar graphs. The Goemans-Williamson SDP relaxation achieves a 0.878-approximation ratio @goemans1995, which is optimal assuming the Unique Games Conjecture. The best known exact algorithm runs in $O^*(2^(omega n slash 3))$ time via algebraic 2-CSP techniques @williams2005, where $omega < 2.372$ is the matrix multiplication exponent.
*Example.* Consider the house graph $G$ with $n = 5$ vertices, $|E| = 6$ edges, and unit weights $w(e) = 1$. The partition $S = {v_0, v_3}$, $overline(S) = {v_1, v_2, v_4}$ cuts 5 of 6 edges: $(v_0, v_1)$, $(v_0, v_2)$, $(v_1, v_3)$, $(v_2, v_3)$, $(v_3, v_4)$. Only the edge $(v_2, v_4)$ is uncut (both endpoints in $overline(S)$). The cut value is $sum w(e) = 5$.
#figure({
let hg = house-graph()
let side-s = (0, 3)
let cut-edges = hg.edges.filter(e => side-s.contains(e.at(0)) != side-s.contains(e.at(1)))
draw-edge-highlight(hg.vertices, hg.edges, cut-edges, side-s)
},
caption: [The house graph with max cut $S = {v_0, v_3}$ (blue) vs $overline(S) = {v_1, v_2, v_4}$ (white). Cut edges shown in bold blue; 5 of 6 edges are cut.],
) <fig:house-maxcut>
]
#problem-def("KColoring")[
Given $G = (V, E)$ and $k$ colors, find $c: V -> {1, ..., k}$ minimizing $|{(u, v) in E : c(u) = c(v)}|$.
][
Graph coloring arises in register allocation, frequency assignment, and scheduling @garey1979. Deciding $k$-colorability is NP-complete for $k >= 3$ but solvable in $O(n+m)$ for $k=2$ via bipartiteness testing. For $k = 3$, the best known algorithm runs in $O^*(1.3289^n)$ @beigel2005; for $k = 4$ in $O^*(1.7159^n)$ @wu2024; for $k = 5$ in $O^*((2-epsilon)^n)$ @zamir2021. In general, inclusion-exclusion achieves $O^*(2^n)$ @bjorklund2009.
*Example.* Consider the house graph $G$ with $k = 3$ colors. The coloring $c(v_0) = 1$, $c(v_1) = 2$, $c(v_2) = 2$, $c(v_3) = 1$, $c(v_4) = 3$ is proper: no adjacent pair shares a color, so the number of conflicts is 0. The house graph has chromatic number $chi(G) = 3$ because the triangle $(v_2, v_3, v_4)$ requires 3 colors.
#figure({
let hg = house-graph()
draw-node-colors(hg.vertices, hg.edges, (0, 1, 1, 0, 2))
},
caption: [A proper 3-coloring of the house graph. Colors: $c(v_0) = c(v_3) = 1$ (blue), $c(v_1) = c(v_2) = 2$ (red), $c(v_4) = 3$ (teal). Zero conflicts.],
) <fig:house-coloring>
]
#problem-def("MinimumDominatingSet")[
Given $G = (V, E)$ with weights $w: V -> RR$, find $S subset.eq V$ minimizing $sum_(v in S) w(v)$ s.t. $forall v in V: v in S or exists u in S: (u, v) in E$.
][
Dominating Set models facility location: each vertex in $S$ "covers" itself and its neighbors. Applications include wireless sensor placement and social network influence maximization. W[2]-complete when parameterized by solution size $k$, making it strictly harder than Vertex Cover in the parameterized hierarchy. The best known exact algorithm runs in $O^*(1.4969^n)$ via measure-and-conquer @vanrooij2011.
*Example.* Consider the house graph $G$ with $n = 5$ vertices and unit weights $w(v) = 1$. The set $S = {v_2, v_3}$ is a minimum dominating set with $w(S) = 2$: vertex $v_2$ dominates ${v_0, v_4}$ and $v_3$ dominates ${v_1}$ (both also dominate each other). No single vertex can dominate all others, so $gamma(G) = 2$.
#figure({
let hg = house-graph()
draw-node-highlight(hg.vertices, hg.edges, (2, 3))
},
caption: [The house graph with minimum dominating set $S = {v_2, v_3}$ (blue, $gamma(G) = 2$). Every white vertex is adjacent to at least one blue vertex.],
) <fig:house-ds>
]
#problem-def("MaximumMatching")[
Given $G = (V, E)$ with weights $w: E -> RR$, find $M subset.eq E$ maximizing $sum_(e in M) w(e)$ s.t. $forall e_1, e_2 in M: e_1 inter e_2 = emptyset$.
][
Unlike most combinatorial optimization problems on general graphs, maximum matching is solvable in polynomial time $O(n^3)$ by Edmonds' blossom algorithm @edmonds1965, which introduced the technique of shrinking odd cycles into pseudo-nodes. Matching theory underpins assignment problems, network flows, and the Tutte-Berge formula for matching deficiency.
*Example.* Consider the house graph $G$ with $n = 5$ vertices, $|E| = 6$ edges, and unit weights $w(e) = 1$. A maximum matching is $M = {(v_0, v_1), (v_2, v_4)}$ with $w(M) = 2$. Each matched edge is vertex-disjoint from the others. Vertex $v_3$ is unmatched; since $n$ is odd, no perfect matching exists.
#figure({
let hg = house-graph()
draw-edge-highlight(hg.vertices, hg.edges, ((0, 1), (2, 4)), (0, 1, 2, 4))
},
caption: [The house graph with a maximum matching $M = {(v_0, v_1), (v_2, v_4)}$ (blue edges, $w(M) = 2$). Matched vertices shown in blue; $v_3$ is unmatched.],
) <fig:house-matching>
]
#problem-def("TravelingSalesman")[
Given an undirected graph $G=(V,E)$ with edge weights $w: E -> RR$, find an edge set $C subset.eq E$ that forms a cycle visiting every vertex exactly once and minimizes $sum_(e in C) w(e)$.
][
One of the most intensely studied NP-hard problems, with applications in logistics, circuit board drilling, and DNA sequencing. The best known exact algorithm runs in $O^*(2^n)$ time and space via Held-Karp dynamic programming @heldkarp1962. No $O^*((2-epsilon)^n)$ algorithm is known, and improving the exponential space remains open.
*Example.* Consider the complete graph $K_4$ with vertices ${v_0, v_1, v_2, v_3}$ and edge weights $w(v_0, v_1) = 1$, $w(v_1, v_2) = 2$, $w(v_2, v_3) = 1$, $w(v_0, v_3) = 2$, $w(v_0, v_2) = 3$, $w(v_1, v_3) = 3$. The optimal tour is $v_0 -> v_1 -> v_2 -> v_3 -> v_0$ with cost $1 + 2 + 1 + 2 = 6$. The tour using diagonals, $v_0 -> v_2 -> v_1 -> v_3 -> v_0$, costs $3 + 2 + 3 + 2 = 10$.
#figure({
let verts = ((0, 0), (1.5, 0), (1.5, 1.5), (0, 1.5))
let all-edges = ((0,1),(1,2),(2,3),(0,3),(0,2),(1,3))
let tour = ((0,1),(1,2),(2,3),(0,3))
let weights = ("1", "2", "1", "2", "3", "3")
canvas(length: 1cm, {
for (idx, (u, v)) in all-edges.enumerate() {
let on-tour = tour.any(t => (t.at(0) == u and t.at(1) == v) or (t.at(0) == v and t.at(1) == u))
g-edge(verts.at(u), verts.at(v),
stroke: if on-tour { 2pt + graph-colors.at(0) } else { 1pt + luma(200) })
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
// offset diagonal labels to avoid overlap
let dx = if u == 0 and v == 2 { -0.25 } else if u == 1 and v == 3 { 0.25 } else { 0 }
let dy = if u == 0 and v == 2 { 0.15 } else if u == 1 and v == 3 { 0.15 } else { 0 }
draw.content((mx + dx, my + dy), text(7pt, fill: luma(80))[#weights.at(idx)])
}
for (k, pos) in verts.enumerate() {
g-node(pos, name: "v" + str(k),
fill: graph-colors.at(0),
label: text(fill: white)[$v_#k$])
}
})
},
caption: [Complete graph $K_4$ with weighted edges. The optimal tour $v_0 -> v_1 -> v_2 -> v_3 -> v_0$ (blue edges) has cost 6.],
) <fig:k4-tsp>
]
#problem-def("MaximumClique")[
Given $G = (V, E)$, find $K subset.eq V$ maximizing $|K|$ such that all pairs in $K$ are adjacent: $forall u, v in K: (u, v) in E$. Equivalent to MIS on the complement graph $overline(G)$.
][
Maximum Clique arises in social network analysis (finding tightly-connected communities), bioinformatics (protein interaction clusters), and coding theory. The problem is equivalent to Maximum Independent Set on the complement graph $overline(G)$. The best known algorithm runs in $O^*(1.1996^n)$ via the complement reduction to MIS @xiao2017. Robson's direct backtracking algorithm achieves $O^*(1.1888^n)$ using exponential space @robson2001.
*Example.* Consider the house graph $G$ with $n = 5$ vertices and $|E| = 6$ edges. The triangle $K = {v_2, v_3, v_4}$ is a maximum clique of size $omega(G) = 3$: all three pairs $(v_2, v_3)$, $(v_2, v_4)$, $(v_3, v_4)$ are edges. No 4-clique exists because vertices $v_0$ and $v_1$ each have degree 2 and are not adjacent to all of ${v_2, v_3, v_4}$.
#figure({
let hg = house-graph()
draw-edge-highlight(hg.vertices, hg.edges, ((2,3), (2,4), (3,4)), (2, 3, 4))
},
caption: [The house graph with maximum clique $K = {v_2, v_3, v_4}$ (blue, $omega(G) = 3$). All edges within the clique are shown in bold blue.],
) <fig:house-clique>
]
#problem-def("MaximalIS")[
Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ maximizing $sum_(v in S) w(v)$ such that $S$ is independent ($forall u, v in S: (u, v) in.not E$) and maximal (no vertex $u in V backslash S$ can be added to $S$ while maintaining independence).
][
The maximality constraint (no vertex can be added) distinguishes this from MIS, which only requires maximum weight. Every maximum independent set is maximal, but not vice versa. The enumeration bound of $O^*(3^(n slash 3))$ for listing all maximal independent sets @tomita2006 is tight: Moon and Moser @moonmoser1965 showed every $n$-vertex graph has at most $3^(n slash 3)$ maximal independent sets, achieved by disjoint triangles.
*Example.* Consider the path graph $P_5$ with $n = 5$ vertices, edges $(v_i, v_(i+1))$ for $i = 0, ..., 3$, and unit weights $w(v) = 1$. The set $S = {v_1, v_3}$ is a maximal independent set: no two vertices in $S$ are adjacent, and neither $v_0$ (adjacent to $v_1$), $v_2$ (adjacent to both), nor $v_4$ (adjacent to $v_3$) can be added. However, $S' = {v_0, v_2, v_4}$ with $w(S') = 3$ is a strictly larger maximal IS, illustrating that maximality does not imply maximum weight.
#figure({
draw-node-highlight(((0, 0), (1, 0), (2, 0), (3, 0), (4, 0)), ((0,1),(1,2),(2,3),(3,4)), (1, 3))
},
caption: [Path $P_5$ with maximal IS $S = {v_1, v_3}$ (blue, $w(S) = 2$). $S$ is maximal — no white vertex can be added — but not maximum: ${v_0, v_2, v_4}$ achieves $w = 3$.],
) <fig:path-maximal-is>
]
== Set Problems
#problem-def("MaximumSetPacking")[
Given universe $U$, collection $cal(S) = {S_1, ..., S_m}$ with $S_i subset.eq U$, weights $w: cal(S) -> RR$, find $cal(P) subset.eq cal(S)$ maximizing $sum_(S in cal(P)) w(S)$ s.t. $forall S_i, S_j in cal(P): S_i inter S_j = emptyset$.
][
One of Karp's 21 NP-complete problems @karp1972. Generalizes maximum matching (the special case where all sets have size 2, solvable in polynomial time). Applications include resource allocation, VLSI design, and frequency assignment. The optimization version is as hard to approximate as maximum clique. The best known exact algorithm runs in $O^*(2^m)$ by brute-force enumeration over the $m$ sets#footnote[No algorithm improving on brute-force enumeration is known for general weighted set packing.].
*Example.* Let $U = {1, 2, 3, 4, 5}$ and $cal(S) = {S_1, S_2, S_3, S_4}$ with $S_1 = {1, 2}$, $S_2 = {2, 3}$, $S_3 = {3, 4}$, $S_4 = {4, 5}$, and unit weights $w(S_i) = 1$. A maximum packing is $cal(P) = {S_1, S_3}$ with $w(cal(P)) = 2$: $S_1 inter S_3 = emptyset$. Adding $S_2$ would conflict with both ($S_1 inter S_2 = {2}$, $S_2 inter S_3 = {3}$), and $S_4$ conflicts with $S_3$ ($S_3 inter S_4 = {4}$). The alternative packing ${S_2, S_4}$ also achieves weight 2.
#figure(
canvas(length: 1cm, {
// Element positions along a line
let elems = ((0, 0), (1, 0), (2, 0), (3, 0), (4, 0))
// Set regions: S1={1,2}, S2={2,3}, S3={3,4}, S4={4,5}
// Selected packing {S1, S3} in blue, others in gray
sregion(((0, 0), (1, 0)), label: [$S_1$], ..sregion-selected)
sregion(((1, 0), (2, 0)), label: [$S_2$], ..sregion-dimmed)
sregion(((2, 0), (3, 0)), label: [$S_3$], ..sregion-selected)
sregion(((3, 0), (4, 0)), label: [$S_4$], ..sregion-dimmed)
// Elements
for (k, pos) in elems.enumerate() {
selem(pos, label: [#(k + 1)], fill: black)
}
}),
caption: [Maximum set packing: $cal(P) = {S_1, S_3}$ (blue) are disjoint; $S_2, S_4$ (gray) conflict with the packing.],
) <fig:set-packing>
]
#problem-def("MinimumSetCovering")[
Given universe $U$, collection $cal(S)$ with weights $w: cal(S) -> RR$, find $cal(C) subset.eq cal(S)$ minimizing $sum_(S in cal(C)) w(S)$ s.t. $union.big_(S in cal(C)) S = U$.
][
One of Karp's 21 NP-complete problems @karp1972. Arises in facility location, crew scheduling, and test suite minimization. The greedy algorithm achieves an $O(ln n)$-approximation where $n = |U|$, which is essentially optimal: cannot be approximated within $(1-o(1)) ln n$ unless P = NP. The best known exact algorithm runs in $O^*(2^m)$ by brute-force enumeration over the $m$ sets#footnote[No algorithm improving on brute-force enumeration is known for general weighted set covering.].
*Example.* Let $U = {1, 2, 3, 4, 5}$ and $cal(S) = {S_1, S_2, S_3}$ with $S_1 = {1, 2, 3}$, $S_2 = {2, 4}$, $S_3 = {3, 4, 5}$, and unit weights $w(S_i) = 1$. A minimum cover is $cal(C) = {S_1, S_3}$ with $w(cal(C)) = 2$: $S_1 union S_3 = {1, 2, 3, 4, 5} = U$. No single set covers all of $U$, so at least two sets are required.
#figure(
canvas(length: 1cm, {
// 2D layout: S1={1,2,3} left, S3={3,4,5} right, S2={2,4} bridging bottom
let elems = (
(-1.2, 0.4), // 1: only S1
(-0.5, -0.4), // 2: S1 ∩ S2
(0.3, 0.4), // 3: S1 ∩ S3
(1.0, -0.4), // 4: S2 ∩ S3
(1.7, 0.4), // 5: only S3
)
// Set regions: S1={1,2,3}, S2={2,4}, S3={3,4,5}
sregion((elems.at(0), elems.at(1), elems.at(2)), pad: 0.4, label: [$S_1$], ..sregion-selected)
sregion((elems.at(1), elems.at(3)), pad: 0.35, label: [$S_2$], ..sregion-dimmed)
sregion((elems.at(2), elems.at(3), elems.at(4)), pad: 0.4, label: [$S_3$], ..sregion-selected)
// Elements
for (k, pos) in elems.enumerate() {
selem(pos, label: [#(k + 1)], fill: black)
}
}),
caption: [Minimum set covering: $cal(C) = {S_1, S_3}$ (blue) cover all of $U$; $S_2$ (gray) is redundant.],
) <fig:set-covering>
]
== Optimization Problems
#problem-def("SpinGlass")[
Given $n$ spin variables $s_i in {-1, +1}$, pairwise couplings $J_(i j) in RR$, and external fields $h_i in RR$, minimize the Hamiltonian (energy function): $H(bold(s)) = -sum_((i,j)) J_(i j) s_i s_j - sum_i h_i s_i$.
][
The Ising spin glass is the canonical model in statistical mechanics for disordered magnetic systems @barahona1982. Ground-state computation is NP-hard on general interaction graphs but polynomial-time solvable on planar graphs without external field ($h_i = 0$) via reduction to minimum-weight perfect matching. Central to quantum annealing, where hardware natively encodes spin Hamiltonians. The best known general algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[On general interaction graphs, no algorithm improving on brute-force enumeration is known.].
*Example.* Consider $n = 5$ spins on a triangular lattice with uniform antiferromagnetic couplings $J_(i j) = -1$ for all edges and no external field ($h_i = 0$). The Hamiltonian simplifies to $H(bold(s)) = sum_((i,j)) s_i s_j$, which counts parallel pairs minus antiparallel pairs. The lattice contains 7 edges and 3 triangular faces; since each triangle cannot have all three pairs antiparallel, frustration is unavoidable. A ground state is $bold(s) = (+, -, +, +, -)$ achieving $H = -3$: five edges are satisfied (antiparallel) and two are frustrated (parallel). No configuration can satisfy more than 5 of 7 edges.
#figure(
canvas(length: 1cm, {
let h = calc.sqrt(3) / 2
let pos = ((0, h), (1, h), (2, h), (0.5, 0), (1.5, 0))
let edges = ((0,1), (1,2), (3,4), (0,3), (1,3), (1,4), (2,4))
let spins = (1, -1, 1, 1, -1)
// Draw edges: black solid = satisfied, dashed gray = frustrated
for (u, v) in edges {
let sat = spins.at(u) * spins.at(v) < 0
g-edge(pos.at(u), pos.at(v),
stroke: if sat { 1pt + black } else { (paint: rgb("#cc4444"), thickness: 1.2pt, dash: "dashed") })
}
// Draw spins: blue = +1, red = −1
for (k, p) in pos.enumerate() {
let up = spins.at(k) > 0
g-node(p, name: "s" + str(k), radius: 0.22,
fill: if up { graph-colors.at(0) } else { graph-colors.at(1) },
label: text(fill: white, if up { $+$ } else { $-$ }))
}
}),
caption: [Triangular lattice with $n = 5$ spins and antiferromagnetic couplings ($J = -1$). Ground state $bold(s) = (+, -, +, +, -)$ with $H = -3$. Solid edges: satisfied (antiparallel); dashed red: frustrated (parallel).],
) <fig:spin-glass>
]
#problem-def("QUBO")[
Given $n$ binary variables $x_i in {0, 1}$, upper-triangular matrix $Q in RR^(n times n)$, minimize $f(bold(x)) = sum_(i=1)^n Q_(i i) x_i + sum_(i < j) Q_(i j) x_i x_j$ (using $x_i^2 = x_i$ for binary variables).
][
Equivalent to the Ising model via the linear substitution $s_i = 2x_i - 1$. The native formulation for quantum annealing hardware (e.g., D-Wave) and a standard target for penalty-method reductions @glover2019. QUBO unifies many combinatorial problems into a single unconstrained binary framework, making it a universal intermediate representation for quantum and classical optimization. The best known general algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[QUBO inherits the Ising model's complexity; no algorithm improving on brute-force is known for the general case.].
*Example.* Consider $n = 3$ with $Q = mat(-1, 2, 0; 0, -1, 2; 0, 0, -1)$. The objective is $f(bold(x)) = -x_1 - x_2 - x_3 + 2x_1 x_2 + 2x_2 x_3$. Evaluating all $2^3$ assignments: $f(0,0,0) = 0$, $f(1,0,0) = -1$, $f(0,1,0) = -1$, $f(0,0,1) = -1$, $f(1,1,0) = 0$, $f(0,1,1) = 0$, $f(1,0,1) = -2$, $f(1,1,1) = 1$. The minimum is $f^* = -2$ at $bold(x)^* = (1, 0, 1)$: selecting $x_1$ and $x_3$ avoids the penalty terms $2x_1 x_2$ and $2x_2 x_3$.
]
#problem-def("ILP")[
Given $n$ integer variables $bold(x) in ZZ^n$, constraint matrix $A in RR^(m times n)$, bounds $bold(b) in RR^m$, and objective $bold(c) in RR^n$, find $bold(x)$ minimizing $bold(c)^top bold(x)$ subject to $A bold(x) <= bold(b)$ and variable bounds.
][
Integer Linear Programming is a universal modeling framework: virtually every NP-hard combinatorial optimization problem admits an ILP formulation. Relaxing integrality to $bold(x) in RR^n$ yields a linear program solvable in polynomial time, forming the basis of branch-and-bound solvers. When the number of integer variables $n$ is fixed, ILP is solvable in polynomial time by Lenstra's algorithm @lenstra1983 using the geometry of numbers, making it fixed-parameter tractable in $n$. The best known general algorithm achieves $O^*(n^n)$ via an FPT algorithm based on lattice techniques @dadush2012.
*Example.* Minimize $bold(c)^top bold(x) = -5x_1 - 6x_2$ subject to $x_1 + x_2 <= 5$, $4x_1 + 7x_2 <= 28$, $x_1, x_2 >= 0$, $bold(x) in ZZ^2$. The LP relaxation optimum is $p_1 = (7 slash 3, 8 slash 3) approx (2.33, 2.67)$ with value $approx -27.67$, which is non-integral. Branch-and-bound yields the ILP optimum $bold(x)^* = (3, 2)$ with $bold(c)^top bold(x)^* = -27$.
#figure(
canvas(length: 0.8cm, {
// Axes
draw.line((-0.3, 0), (5.5, 0), mark: (end: "straight"), stroke: 0.6pt)
draw.line((0, -0.3), (0, 4.8), mark: (end: "straight"), stroke: 0.6pt)
draw.content((5.7, -0.15), text(8pt)[$x_1$])
draw.content((-0.15, 5.0), text(8pt)[$x_2$])
// Tick marks
for i in range(1, 6) {
draw.line((i, -0.08), (i, 0.08), stroke: 0.4pt)
draw.content((i, -0.35), text(6pt)[#i])
}
for i in range(1, 5) {
draw.line((-0.08, i), (0.08, i), stroke: 0.4pt)
draw.content((-0.35, i), text(6pt)[#i])
}
// Feasible region polygon: (0,0) → (5,0) → (7/3, 8/3) → (0, 4)
draw.line((0,0), (5,0), (7/3, 8/3), (0, 4), close: true,
fill: green.lighten(70%), stroke: none)
// Constraint lines (extending beyond feasible region)
draw.line((0, 5), (5, 0), stroke: graph-colors.at(0)) // x1 + x2 = 5
draw.line((0, 4), (5.25, 1), stroke: orange) // 4x1 + 7x2 = 28
// Objective function level curve (dashed): -5x1 - 6x2 = -23, i.e. x2 = (23 - 5x1)/6
draw.line((0, 23/6), (23/5, 0), stroke: (paint: luma(80), dash: "dashed"))
// Gradient direction arrow
draw.line((1.5, 2.5), (1.1, 1.9), mark: (end: "straight"), stroke: 1pt + luma(80))
draw.content((0.7, 1.75), text(6pt, fill: luma(80))[$bold(c)$])
// Constraint labels
draw.content((4.3, 1.0), text(6pt, fill: graph-colors.at(0))[$x_1 + x_2 = 5$], anchor: "west")
draw.content((4.5, 1.7), text(6pt, fill: orange)[$4x_1 + 7x_2 = 28$], anchor: "west")
draw.content((1.2, 4.3), text(6pt, fill: luma(80))[objective], anchor: "south")
// Integer lattice points (hollow circles)
for x1 in range(6) {
for x2 in range(5) {
draw.circle((x1, x2), radius: 0.06, fill: none, stroke: 0.4pt + luma(120))
}
}
// LP optimum (fractional, non-integer)
draw.circle((7/3, 8/3), radius: 0.1, fill: graph-colors.at(1), stroke: none)
draw.content((7/3 + 0.3, 8/3 + 0.3), text(7pt)[$p_1$])
// ILP optimum (integer)
draw.circle((3, 2), radius: 0.1, fill: graph-colors.at(1), stroke: none)
draw.content((3.3, 2.3), text(7pt)[$bold(x)^*$])
}),
caption: [ILP feasible region (green) with constraints $x_1 + x_2 <= 5$ (blue) and $4x_1 + 7x_2 <= 28$ (orange). Hollow circles mark the integer lattice. The LP relaxation optimum $p_1 = (7 slash 3, 8 slash 3)$ is non-integral; the ILP optimum $bold(x)^* = (3, 2)$ gives $bold(c)^top bold(x)^* = -27$.],
) <fig:ilp-example>
]
#problem-def("ClosestVectorProblem")[
Given a lattice basis $bold(B) in RR^(m times n)$ (columns $bold(b)_1, dots, bold(b)_n in RR^m$ spanning lattice $cal(L)(bold(B)) = {bold(B) bold(x) : bold(x) in ZZ^n}$) and target $bold(t) in RR^m$, find $bold(x) in ZZ^n$ minimizing $norm(bold(B) bold(x) - bold(t))_2$.
][
The Closest Vector Problem is a fundamental lattice problem, proven NP-hard by van Emde Boas @vanemde1981. CVP appears in lattice-based cryptography, coding theory, and integer programming @lenstra1983. Kannan's enumeration algorithm @kannan1987 solves CVP in $n^(O(n))$ time; Micciancio and Voulgaris @micciancio2010 improved this to deterministic $O^*(4^n)$ using Voronoi cell computations, and Aggarwal, Dadush, and Stephens-Davidowitz @aggarwal2015 achieved randomized $O^*(2^n)$.
*Example.* Consider the 2D lattice with basis $bold(b)_1 = (2, 0)^top$, $bold(b)_2 = (1, 2)^top$ and target $bold(t) = (2.8, 1.5)^top$. The lattice points near $bold(t)$ include $bold(B)(1, 0)^top = (2, 0)^top$, $bold(B)(0, 1)^top = (1, 2)^top$, and $bold(B)(1, 1)^top = (3, 2)^top$. The closest is $bold(B)(1, 1)^top = (3, 2)^top$ with distance $norm(bold(B)(1,1)^top - bold(t))_2 = norm((0.2, 0.5))_2 = sqrt(0.04 + 0.25) approx 0.539$.
#figure(
canvas(length: 0.8cm, {
import draw: *
// Lattice points: B*(x1,x2) = x1*(2,0) + x2*(1,2)
for x1 in range(0, 3) {
for x2 in range(0, 3) {
let px = x1 * 2 + x2 * 1
let py = x2 * 2
let is-closest = (x1 == 1 and x2 == 1)
let nm = "p" + str(x1) + str(x2)
circle(
(px, py),
radius: if is-closest { 0.15 } else { 0.08 },
fill: if is-closest { graph-colors.at(0) } else { luma(180) },
stroke: if is-closest { 0.8pt + graph-colors.at(0) } else { 0.4pt + luma(120) },
name: nm,
)
}
}
// Target vector
circle((2.8, 1.5), radius: 0.1, fill: graph-colors.at(1), stroke: none, name: "target")
content((rel: (0, -0.45), to: "target"), text(7pt)[$bold(t)$])
// Dashed line from target to closest point
line("target", "p11", stroke: (paint: graph-colors.at(0), thickness: 0.8pt, dash: "dashed"))
// Basis vectors as arrows from origin
line("p00", "p10", mark: (end: "straight"), stroke: 0.8pt + luma(100), name: "b1")
content((rel: (0, -0.35), to: "b1.mid"), text(7pt)[$bold(b)_1$])
line("p00", "p01", mark: (end: "straight"), stroke: 0.8pt + luma(100), name: "b2")
content((rel: (-0.3, 0), to: "b2.mid"), text(7pt)[$bold(b)_2$])
// Label closest point
content((rel: (0.45, 0.3), to: "p11"), text(7pt)[$bold(B)(1,1)^top$])
}),
caption: [2D lattice with basis $bold(b)_1 = (2, 0)^top$, $bold(b)_2 = (1, 2)^top$. Target $bold(t) = (2.8, 1.5)^top$ (red) and closest lattice point $bold(B)(1,1)^top = (3, 2)^top$ (blue). Distance $norm(bold(B)(1,1)^top - bold(t))_2 approx 0.539$.],
) <fig:cvp-example>
]
== Satisfiability Problems
#problem-def("Satisfiability")[
Given a CNF formula $phi = and.big_(j=1)^m C_j$ with $m$ clauses over $n$ Boolean variables, where each clause $C_j = or.big_i ell_(j i)$ is a disjunction of literals, find an assignment $bold(x) in {0, 1}^n$ such that $phi(bold(x)) = 1$ (all clauses satisfied).
][
The Boolean Satisfiability Problem (SAT) is the first problem proven NP-complete @cook1971. SAT serves as the foundation of NP-completeness theory: showing a new problem NP-hard typically proceeds by reduction from SAT or one of its variants. Despite worst-case hardness, conflict-driven clause learning (CDCL) solvers handle industrial instances with millions of variables. The Strong Exponential Time Hypothesis (SETH) @impagliazzo2001 conjectures that no $O^*((2-epsilon)^n)$ algorithm exists for general CNF-SAT, and the best known algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[SETH conjectures this is optimal; no $O^*((2-epsilon)^n)$ algorithm is known.].
*Example.* Consider $phi = (x_1 or x_2) and (not x_1 or x_3) and (not x_2 or not x_3)$ with $n = 3$ variables and $m = 3$ clauses. The assignment $(x_1, x_2, x_3) = (1, 0, 1)$ satisfies all clauses: $C_1 = (1 or 0) = 1$, $C_2 = (0 or 1) = 1$, $C_3 = (1 or 0) = 1$. Hence $phi(1, 0, 1) = 1$.
]
#problem-def("KSatisfiability")[
SAT with exactly $k$ literals per clause.
][
The restriction of SAT to exactly $k$ literals per clause reveals a sharp complexity transition: 2-SAT is polynomial-time solvable via implication graph SCC decomposition @aspvall1979 in $O(n+m)$, while $k$-SAT for $k >= 3$ is NP-complete. Random $k$-SAT exhibits a satisfiability threshold at clause density $m slash n approx 2^k ln 2$, a key phenomenon in computational phase transitions. The best known algorithm for 3-SAT runs in $O^*(1.307^n)$ via biased-PPSZ @hansen2019. Under SETH, $k$-SAT requires time $O^*(c_k^n)$ with $c_k -> 2$ as $k -> infinity$.
*Example.* Consider the 3-SAT formula $phi = (x_1 or x_2 or x_3) and (not x_1 or not x_2 or x_3) and (x_1 or not x_2 or not x_3)$ with $n = 3$ variables and $m = 3$ clauses, each containing exactly 3 literals. The assignment $(x_1, x_2, x_3) = (1, 0, 1)$ satisfies all clauses: $C_1 = (1 or 0 or 1) = 1$, $C_2 = (0 or 1 or 1) = 1$, $C_3 = (1 or 1 or 0) = 1$.
]
#problem-def("CircuitSAT")[
Given a Boolean circuit $C$ composed of logic gates (AND, OR, NOT, XOR) with $n$ input variables, find an input assignment $bold(x) in {0,1}^n$ such that $C(bold(x)) = 1$.
][
Circuit Satisfiability is the most natural NP-complete problem: the Cook-Levin theorem @cook1971 proves NP-completeness by showing any nondeterministic polynomial-time computation can be encoded as a Boolean circuit. CircuitSAT is strictly more succinct than CNF-SAT, since a circuit with $g$ gates may require an exponentially larger CNF formula without auxiliary variables. The Tseitin transformation reduces CircuitSAT to CNF-SAT with only $O(g)$ clauses by introducing one auxiliary variable per gate. The best known algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[No algorithm improving on brute-force is known for general circuits.].
*Example.* Consider the circuit $C(x_1, x_2) = (x_1 "AND" x_2) "XOR" (x_1 "OR" x_2)$ with $n = 2$ inputs. Evaluating: $C(0,0) = (0) "XOR" (0) = 0$, $C(0,1) = (0) "XOR" (1) = 1$, $C(1,0) = (0) "XOR" (1) = 1$, $C(1,1) = (1) "XOR" (1) = 0$. The satisfying assignments are $(0, 1)$ and $(1, 0)$ -- precisely the inputs where exactly one variable is true.
#figure(
canvas(length: 1cm, {
// Gate positions: AND/OR vertically stacked, XOR to the right
// With inputs=2, w=0.8: h = max(0.5, 0.7) = 0.7, ports at ±0.175 from center
gate-and((2, 0.8), name: "and")
gate-or((2, -0.8), name: "or")
gate-xor((4.5, 0), name: "xor")
// AND → XOR, OR → XOR (right-angle routing)
draw.line("and.out", (3.5, 0.8), (3.5, 0.175), "xor.in0")
draw.line("or.out", (3.5, -0.8), (3.5, -0.175), "xor.in1")
// Output wire and label
draw.line("xor.out", (5.5, 0), mark: (end: ">"))
draw.content((5.8, 0), text(8pt)[$C$])
// x1 fork: to and.in0 (y = 0.975) and or.in0 (y = −0.625)
draw.line((0, 0.975), (0.8, 0.975), "and.in0")
draw.line((0.8, 0.975), (0.8, -0.625), "or.in0")
draw.circle((0.8, 0.975), radius: 0.04, fill: black, stroke: none)
// x2 fork: to or.in1 (y = −0.975) and and.in1 (y = 0.625)
draw.line((0, -0.975), (0.5, -0.975), "or.in1")
draw.line((0.5, -0.975), (0.5, 0.625), "and.in1")
draw.circle((0.5, -0.975), radius: 0.04, fill: black, stroke: none)
// Input labels
draw.content((-0.3, 0.975), text(8pt)[$x_1$])
draw.content((-0.3, -0.975), text(8pt)[$x_2$])
}),
caption: [Circuit $C(x_1, x_2) = (x_1 and x_2) xor (x_1 or x_2)$. Junction dots mark where inputs fork to both gates. Satisfying assignments: $(0,1)$ and $(1,0)$.],
) <fig:circuit-sat>
]
#problem-def("Factoring")[
Given a composite integer $N$ and bit sizes $m, n$, find integers $p in [2, 2^m - 1]$ and $q in [2, 2^n - 1]$ such that $p times q = N$. Here $p$ has $m$ bits and $q$ has $n$ bits.
][
The hardness of integer factorization underpins RSA cryptography and other public-key systems. Unlike most problems in this collection, Factoring is not known to be NP-complete; it lies in NP $inter$ co-NP, suggesting it may be of intermediate complexity. The best classical algorithm is the General Number Field Sieve @lenstra1993 running in sub-exponential time $e^(O(b^(1 slash 3)(log b)^(2 slash 3)))$ where $b$ is the bit length. Shor's algorithm @shor1994 solves Factoring in polynomial time on a quantum computer.
*Example.* Let $N = 15$ with $m = 2$ bits and $n = 3$ bits, so $p in [2, 3]$ and $q in [2, 7]$. The solution is $p = 3$, $q = 5$, since $3 times 5 = 15 = N$. Note $p = 3$ fits in 2 bits and $q = 5$ fits in 3 bits. The alternative factorization $5 times 3$ requires $m = 3$, $n = 2$.
]
== Specialized Problems
#problem-def("BMF")[
Given an $m times n$ boolean matrix $A$ and rank $k$, find boolean matrices $B in {0,1}^(m times k)$ and $C in {0,1}^(k times n)$ minimizing the Hamming distance $d_H (A, B circle.tiny C)$, where the boolean product $(B circle.tiny C)_(i j) = or.big_ell (B_(i ell) and C_(ell j))$.
][
Boolean Matrix Factorization decomposes binary data into interpretable boolean factors, unlike real-valued SVD which loses the discrete structure. NP-hard even to approximate, BMF arises in data mining, text classification, and role-based access control where factors correspond to latent binary features. Practical algorithms use greedy rank-1 extraction or alternating fixed-point methods. The best known exact algorithm runs in $O^*(2^(m k + k n))$ by brute-force search over $B$ and $C$#footnote[No algorithm improving on brute-force enumeration is known for general BMF.].
*Example.* Let $A = mat(1, 1, 0; 1, 1, 1; 0, 1, 1)$ and $k = 2$. Set $B = mat(1, 0; 1, 1; 0, 1)$ and $C = mat(1, 1, 0; 0, 1, 1)$. Then $B circle.tiny C = mat(1, 1, 0; 1, 1, 1; 0, 1, 1) = A$, achieving Hamming distance $d_H = 0$ (exact factorization). The two boolean factors capture overlapping row/column patterns: factor 1 selects rows ${1, 2}$ and columns ${1, 2}$; factor 2 selects rows ${2, 3}$ and columns ${2, 3}$.
#figure(
{
let cell(val, x, y, color) = {
let f = if val == 1 { color.transparentize(30%) } else { white }
box(width: 0.45cm, height: 0.45cm, fill: f, stroke: 0.4pt + luma(180),
align(center + horizon, text(7pt, if val == 1 { [1] } else { [0] })))
}
let mat-grid(data, color) = {
grid(columns: data.at(0).len(), column-gutter: 0pt, row-gutter: 0pt,
..data.flatten().enumerate().map(((i, v)) => {
cell(v, calc.rem(i, data.at(0).len()), int(i / data.at(0).len()), color)
}))
}
let A = ((1,1,0),(1,1,1),(0,1,1))
let B = ((1,0),(1,1),(0,1))
let C = ((1,1,0),(0,1,1))
set text(8pt)
align(center, stack(dir: ltr, spacing: 0.3cm,
[$A =$], mat-grid(A, graph-colors.at(0)),
[$= B circle.tiny C =$],
mat-grid(B, graph-colors.at(1)),
[$circle.tiny$],
mat-grid(C, rgb("#76b7b2")),
))
},
caption: [Boolean matrix factorization: $A = B circle.tiny C$ with rank $k = 2$. Factor 1 (red) covers the top-left block; factor 2 (teal) covers the bottom-right block.],
) <fig:bmf>
]
#problem-def("PaintShop")[
Given a sequence of $2n$ positions where each of $n$ cars appears exactly twice, assign a binary color to each car (each car's two occurrences receive opposite colors) to minimize the number of color changes between consecutive positions.
][
NP-hard and APX-hard @epping2004. Arises in automotive manufacturing where color changes between consecutive cars on an assembly line require costly purging of paint nozzles. Each car appears twice in the sequence (two coats), and each car's two occurrences must receive opposite colors (one per side). A natural benchmark for quantum annealing due to its binary structure and industrial relevance. The best known algorithm runs in $O^*(2^n)$ by brute-force enumeration#footnote[No algorithm improving on brute-force is known for general Paint Shop.].
*Example.* Consider $n = 3$ cars with sequence $(A, B, A, C, B, C)$. Each car gets one occurrence colored 0 and the other colored 1. The assignment $A: 0\/1$, $B: 0\/1$, $C: 1\/0$ yields color sequence $(0, 0, 1, 1, 1, 0)$ with 2 color changes (positions $2 -> 3$ and $5 -> 6$). The alternative $A: 1\/0$, $B: 0\/1$, $C: 0\/1$ yields $(1, 0, 0, 0, 1, 1)$ with 2 changes. The minimum is 2 changes.
#figure(
{
let cars = ("A", "B", "A", "C", "B", "C")
let colors = (0, 0, 1, 1, 1, 0) // optimal assignment
let blue = graph-colors.at(0)
let red = graph-colors.at(1)
align(center, stack(dir: ltr, spacing: 0pt,
..cars.zip(colors).enumerate().map(((i, (car, c))) => {
let fill = if c == 0 { white } else { blue.transparentize(40%) }
let change = if i > 0 and colors.at(i) != colors.at(i - 1) {
place(dx: -0.08cm, dy: 0.55cm, text(6pt, fill: red, weight: "bold")[×])
}
stack(dir: ttb, spacing: 0.08cm,
box(width: 0.55cm, height: 0.55cm, fill: fill, stroke: 0.5pt + luma(120),
align(center + horizon, text(8pt, weight: "bold", car))),
text(6pt, fill: luma(100), str(c)),
change,
)
})))
},
caption: [Paint Shop: sequence $(A, B, A, C, B, C)$ with optimal coloring. White = color 0, blue = color 1. Two color changes (marked ×) at positions $2 -> 3$ and $5 -> 6$.],
) <fig:paintshop>
]
#problem-def("BicliqueCover")[
Given a bipartite graph $G = (L, R, E)$ and integer $k$, find $k$ bicliques $(L_1, R_1), dots, (L_k, R_k)$ that cover all edges ($E subset.eq union.big_i L_i times R_i$) while minimizing the total size $sum_i (|L_i| + |R_i|)$.
][
Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipartite graph as a Boolean sum of rank-1 binary matrices, connecting it to Boolean matrix rank and nondeterministic communication complexity. Applications include data compression, database optimization (covering queries with materialized views), and bioinformatics (gene expression biclustering). NP-hard even for fixed $k >= 2$. The best known algorithm runs in $O^*(2^(|L| + |R|))$ by brute-force enumeration#footnote[No algorithm improving on brute-force enumeration is known for general Biclique Cover.].
*Example.* Consider $G = (L, R, E)$ with $L = {ell_1, ell_2}$, $R = {r_1, r_2, r_3}$, and edges $E = {(ell_1, r_1), (ell_1, r_2), (ell_2, r_2), (ell_2, r_3)}$. A biclique cover with $k = 2$: $(L_1, R_1) = ({ell_1}, {r_1, r_2})$ covering edges ${(ell_1, r_1), (ell_1, r_2)}$, and $(L_2, R_2) = ({ell_2}, {r_2, r_3})$ covering ${(ell_2, r_2), (ell_2, r_3)}$. Total size $= (1+2) + (1+2) = 6$. Merging into a single biclique is impossible since $(ell_1, r_3) in.not E$.
#figure(
canvas(length: 1cm, {
// Bipartite layout: L on left, R on right
let lpos = ((0, 1), (0, 0)) // l1, l2
let rpos = ((2.5, 1.5), (2.5, 0.5), (2.5, -0.5)) // r1, r2, r3
let edges = ((0, 0), (0, 1), (1, 1), (1, 2)) // (li, rj) pairs
// Biclique 1: l1-{r1,r2} in blue; Biclique 2: l2-{r2,r3} in teal
let bc1 = ((0,0), (0,1))
let bc2 = ((1,1), (1,2))
for (li, rj) in edges {
let is-bc1 = bc1.contains((li, rj))
let c = if is-bc1 { graph-colors.at(0) } else { rgb("#76b7b2") }
g-edge(lpos.at(li), rpos.at(rj), stroke: 1.5pt + c)
}
// L nodes
for (k, p) in lpos.enumerate() {
g-node(p, name: "l" + str(k), fill: luma(240), label: $ell_#(k+1)$)
}
// R nodes
for (k, p) in rpos.enumerate() {
g-node(p, name: "r" + str(k), fill: luma(240), label: $r_#(k+1)$)
}
}),
caption: [Biclique cover of a bipartite graph: biclique 1 (blue) $= ({ell_1}, {r_1, r_2})$, biclique 2 (teal) $= ({ell_2}, {r_2, r_3})$. Edge $(ell_1, r_3)$ is absent, preventing a single biclique.],
) <fig:biclique-cover>
]
#problem-def("BinPacking")[
Given $n$ items with sizes $s_1, dots, s_n in RR^+$ and bin capacity $C > 0$, find an assignment $x: {1, dots, n} -> NN$ minimizing $|{x(i) : i = 1, dots, n}|$ (the number of distinct bins used) subject to $forall j: sum_(i: x(i) = j) s_i lt.eq C$.
][
Bin Packing is one of the classical NP-hard optimization problems @garey1979, with applications in logistics, cutting stock, and cloud resource allocation. The best known exact algorithm runs in $O^*(2^n)$ time via inclusion-exclusion over set partitions @bjorklund2009.
*Example.* Consider $n = 6$ items with sizes $(6, 6, 5, 5, 4, 4)$ and capacity $C = 10$. The lower bound is $ceil(30 slash 10) = 3$ bins. An optimal packing uses exactly 3 bins: $B_1 = {6, 4}$, $B_2 = {6, 4}$, $B_3 = {5, 5}$, each with total load $10 = C$.
#figure({
canvas(length: 1cm, {
let s = 0.28
let w = 1.0
let gap = 0.6
let bins = ((6, 4), (6, 4), (5, 5))
let fills = (
(graph-colors.at(0), graph-colors.at(1)),
(graph-colors.at(0), graph-colors.at(1)),
(graph-colors.at(2), graph-colors.at(2)),
)
for i in range(3) {
let x = i * (w + gap)
draw.rect((x, 0), (x + w, 10 * s), stroke: 0.8pt + black)
let y = 0
for j in range(bins.at(i).len()) {
let sz = bins.at(i).at(j)
let c = fills.at(i).at(j)
draw.rect((x, y), (x + w, y + sz * s), stroke: 0.4pt, fill: c)
draw.content((x + w / 2, y + sz * s / 2), text(8pt, fill: white)[#sz])
y += sz * s
}
draw.content((x + w / 2, -0.3), text(8pt)[$B_#(i + 1)$])
}
draw.line((-0.15, 10 * s), (2 * (w + gap) + w + 0.15, 10 * s),
stroke: (dash: "dashed", paint: luma(150), thickness: 0.5pt))
draw.content((-0.5, 10 * s), text(7pt)[$C$])
})
},
caption: [Optimal packing of items with sizes $(6, 6, 5, 5, 4, 4)$ into 3 bins of capacity $C = 10$. Numbers indicate item sizes; all bins are fully utilized.],
) <fig:binpacking-example>
]
// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
let names = graph-data.nodes.map(n => n.name)
let unique = ()
for n in names { if n not in unique { unique.push(n) } }
unique
}
let defined = display-name.keys()
let missing = json-models.filter(n => n not in defined)
if missing.len() > 0 {
block(width: 100%, inset: (x: 1em, y: 0.5em), fill: rgb("#fff3cd"), stroke: (left: 3pt + rgb("#ffc107")))[
#text(fill: rgb("#856404"), weight: "bold")[Warning: Missing problem definitions for:]
#text(fill: rgb("#856404"))[ #missing.join(", ")]
]
}
}
= Reductions <sec:reductions>
Each reduction is presented as a *Rule* (with linked problem names and overhead from the graph data), followed by a *Proof* (construction, correctness, variable mapping, solution extraction), and optionally a *Concrete Example* (a small instance with verified solution). Problem names in the rule title link back to their definitions in @sec:problems.
== Trivial Reductions
#let mvc_mis = load-example("minimumvertexcover_to_maximumindependentset")
#let mvc_mis_r = load-results("minimumvertexcover_to_maximumindependentset")
#let mvc_mis_sol = mvc_mis_r.solutions.at(0)
#reduction-rule("MinimumVertexCover", "MaximumIndependentSet",
example: true,
example-caption: [Petersen graph ($n = 10$): VC $arrow.l.r$ IS],
extra: [
Source VC: $C = {#mvc_mis_sol.source_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => str(i)).join(", ")}$ (size #mvc_mis_sol.source_config.filter(x => x == 1).len()) #h(1em)
Target IS: $S = {#mvc_mis_sol.target_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => str(i)).join(", ")}$ (size #mvc_mis_sol.target_config.filter(x => x == 1).len()) \
$|"VC"| + |"IS"| = #mvc_mis.source.instance.num_vertices = |V|$ #sym.checkmark
],
)[
Vertex cover and independent set are set complements: removing a cover from $V$ leaves vertices with no edges between them (an independent set), and vice versa. Since $|S| + |C| = |V|$ is constant, maximizing one is equivalent to minimizing the other. The reduction preserves the graph and weights unchanged.
][
_Construction._ Given VC instance $(G, bold(w))$, create IS instance $(G, bold(w))$ with identical graph and weights. Variables correspond one-to-one: vertex $v$ in the source maps to vertex $v$ in the target.
_Correctness._ ($arrow.r.double$) If $C$ is a vertex cover, then for any $u, v in V backslash C$, the edge $(u, v) in.not E$ (otherwise $C$ would miss it), so $V backslash C$ is independent. ($arrow.l.double$) If $S$ is independent, then for any $(u, v) in E$, at most one endpoint lies in $S$, so $V backslash S$ covers every edge. Since $|S| + |C| = |V|$ is constant, a minimum vertex cover corresponds to a maximum independent set.
_Solution extraction._ For IS solution $S$, return $C = V backslash S$, i.e.\ flip each variable: $c_v = 1 - s_v$.
]
#reduction-rule("MaximumIndependentSet", "MinimumVertexCover")[
The exact reverse of VC $arrow.r$ IS: complementing an independent set yields a vertex cover. The graph and weights are preserved unchanged, and $|"IS"| + |"VC"| = |V|$ ensures optimality carries over.
][
_Construction._ Given IS instance $(G, bold(w))$, create VC instance $(G, bold(w))$ with identical graph and weights.
_Correctness._ ($arrow.r.double$) If $S$ is independent, no edge has both endpoints in $S$, so every edge has at least one endpoint in $V backslash S$, making $V backslash S$ a cover. ($arrow.l.double$) If $C$ is a vertex cover, every edge is incident to some vertex in $C$, so no edge connects two vertices of $V backslash C$, making $V backslash C$ independent.
_Solution extraction._ For VC solution $C$, return $S = V backslash C$, i.e.\ flip each variable: $s_v = 1 - c_v$.
]
#reduction-rule("MaximumIndependentSet", "MaximumSetPacking")[
The key insight is that two vertices are adjacent if and only if they share an edge. By representing each vertex $v$ as the set of its incident edges $S_v$, adjacency becomes set overlap: $S_u inter S_v != emptyset$ iff $(u,v) in E$. Thus an independent set (no two adjacent) maps exactly to a packing (no two overlapping).
][
_Construction._ Universe $U = E$ (edges, indexed $0, ..., |E|-1$). For each vertex $v$, define $S_v = {e in E : v in e}$ (the set of edge indices incident to $v$), with weight $w(S_v) = w(v)$. Variables correspond one-to-one: vertex $v$ maps to set $S_v$.
_Correctness._ ($arrow.r.double$) If $I$ is independent, then for any $u, v in I$, edge $(u,v) in.not E$, so $S_u inter S_v = emptyset$ — the sets are mutually disjoint, forming a valid packing. ($arrow.l.double$) If ${S_v : v in P}$ is a packing, then for any $u, v in P$, $S_u inter S_v = emptyset$, meaning $u$ and $v$ share no edge, so $P$ is independent. Weight sums are identical, so optimality is preserved.
_Solution extraction._ For packing ${S_v : v in P}$, return IS $= P$ (same variable assignment).
]
#reduction-rule("MaximumSetPacking", "MaximumIndependentSet")[
The _intersection graph_ captures set overlap as adjacency: two sets that share an element become neighbors, so a packing (mutually disjoint sets) corresponds exactly to an independent set (mutually non-adjacent vertices). This is the standard reduction from set packing to independent set.
][
_Construction._ Build the intersection graph $G' = (V', E')$: create one vertex $v_i$ per set $S_i$ ($i = 1, ..., m$), and add edge $(v_i, v_j)$ iff $S_i inter S_j != emptyset$. Set $w(v_i) = w(S_i)$. Variables correspond one-to-one: set $S_i$ maps to vertex $v_i$.
_Correctness._ ($arrow.r.double$) If $cal(P)$ is a packing (all sets mutually disjoint), then for any $S_i, S_j in cal(P)$, $S_i inter S_j = emptyset$, so $(v_i, v_j) in.not E'$, meaning ${v_i : S_i in cal(P)}$ is independent. ($arrow.l.double$) If $I subset.eq V'$ is independent, then for any $v_i, v_j in I$, $(v_i, v_j) in.not E'$, so $S_i inter S_j = emptyset$, meaning ${S_i : v_i in I}$ is a valid packing. Weight sums match, so optimality is preserved.
_Solution extraction._ For IS $I subset.eq V'$, return packing $cal(P) = {S_i : v_i in I}$ (same variable assignment).
]
#reduction-rule("MinimumVertexCover", "MinimumSetCovering")[
A vertex cover must "hit" every edge; set covering must "hit" every universe element. By making each edge a universe element and each vertex the set of its incident edges, the two covering conditions become identical. This is the canonical embedding of vertex cover as a special case of set covering.
][
_Construction._ Universe $U = {0, ..., |E|-1}$ (one element per edge). For each vertex $v$, define $S_v = {i : e_i "incident to" v}$ (the indices of edges touching $v$), with weight $w(S_v) = w(v)$. Variables correspond one-to-one: vertex $v$ maps to set $S_v$.
_Correctness._ ($arrow.r.double$) If $C$ is a vertex cover, every edge $e_i$ has at least one endpoint $v in C$, so $i in S_v$ for some selected set — hence $union.big_(v in C) S_v = U$, a valid covering. ($arrow.l.double$) If ${S_v : v in C}$ covers $U$, then every edge index $i in U$ appears in some $S_v$ with $v in C$, meaning edge $e_i$ is incident to some $v in C$ — hence $C$ is a vertex cover. Weight sums are identical, so optimality is preserved.
_Solution extraction._ For covering ${S_v : v in C}$, return VC $= C$ (same variable assignment).
]
#reduction-rule("MaximumMatching", "MaximumSetPacking")[
A matching selects edges that share no endpoints; set packing selects sets that share no elements. By representing each edge as the 2-element set of its endpoints and using vertices as the universe, two edges conflict (share an endpoint) if and only if their sets overlap. This embeds matching as a special case of set packing where every set has size exactly 2.
][
_Construction._ Universe $U = V$ (vertices, indexed $0, ..., |V|-1$). For each edge $e = (u, v)$, define $S_e = {u, v}$ with weight $w(S_e) = w(e)$. Variables correspond one-to-one: edge $e$ maps to set $S_e$.
_Correctness._ ($arrow.r.double$) If $M$ is a matching, then for any $e_1, e_2 in M$, the edges share no endpoint, so $S_(e_1) inter S_(e_2) = emptyset$ — the sets are mutually disjoint, forming a valid packing. ($arrow.l.double$) If ${S_e : e in P}$ is a packing, then for any $e_1, e_2 in P$, $S_(e_1) inter S_(e_2) = emptyset$, meaning the edges share no vertex, so $P$ is a valid matching. Weight sums are identical, so optimality is preserved.
_Solution extraction._ For packing ${S_e : e in P}$, return matching $= P$ (same variable assignment).
]
#reduction-rule("QUBO", "SpinGlass")[
QUBO uses binary variables $x_i in {0,1}$; the Ising model uses spin variables $s_i in {-1,+1}$. The affine substitution $x_i = (s_i + 1)\/2$ converts between the two encodings. Since every quadratic binary function maps to a quadratic spin function (and vice versa), the two models are polynomially equivalent. This is the reverse of SpinGlass $arrow.r$ QUBO.
][
_Construction._ Substitute $x_i = (s_i + 1)\/2$ into $f(bold(x)) = sum_(i <= j) Q_(i j) x_i x_j$. For diagonal terms ($i = j$): $Q_(i i) x_i = Q_(i i)(s_i + 1)\/2$, contributing $Q_(i i)\/2$ to $h_i$. For off-diagonal terms ($i < j$): $Q_(i j) x_i x_j = Q_(i j)(s_i + 1)(s_j + 1)\/4$, contributing $Q_(i j)\/4$ to $J_(i j)$, $Q_(i j)\/4$ to both $h_i$ and $h_j$, plus a constant. Collecting terms:
$ J_(i j) = Q_(i j) / 4, quad h_i = 1/2 (Q_(i i) + sum_(j != i) Q_(i j) / 2) $
_Correctness._ ($arrow.r.double$) Any binary assignment $bold(x)$ maps to a spin assignment $bold(s)$ with $s_i = 2 x_i - 1$, and the QUBO objective equals the Ising energy up to a global constant. ($arrow.l.double$) Any spin ground state maps back to a binary minimizer via $x_i = (s_i + 1)\/2$. The constant offset does not affect the argmin.
_Solution extraction._ Convert spins to binary: $x_i = (s_i + 1) \/ 2$, i.e.\ $s_i = +1 arrow.r x_i = 1$, $s_i = -1 arrow.r x_i = 0$.
]
#let sg_qubo = load-example("spinglass_to_qubo")
#let sg_qubo_r = load-results("spinglass_to_qubo")
#let sg_qubo_sol = sg_qubo_r.solutions.at(0)
#reduction-rule("SpinGlass", "QUBO",
example: true,
example-caption: [10-spin Ising model on Petersen graph],