The oldest and most common technique for solving NP-hard optimization problems in practice is [(Mixed) Integer (Linear) Programming](https://en.wikipedia.org/wiki/Integer_programming).
It allows modelling problems using linear constraints and objective functions on fractional and integral variables.
Most combinatorial problems are easily expressable by Mixed Integer Programming, and most practical optimization problems can be sufficiently approximated.
The probably most famous sucess story of this technique is its usage by the [TSP-solver Concorde](https://en.wikipedia.org/wiki/Concorde_TSP_Solver), which was able to solve a (non-trivial) instance with over 85000 vertices to optimality.
The solvers are usually based on [Branch&Bound](https://en.wikipedia.org/wiki/Branch_and_bound), [Linear Relaxation](https://en.wikipedia.org/wiki/Linear_programming_relaxation), and [Cutting Planes](https://en.wikipedia.org/wiki/Cutting-plane_method), of which especially the last two have a serious mathematical foundation.
Mixed Integer Programming is one of the prime examples for the importance of theoretical research for practical progress.
However, you don't have to worry about any mathematical details, as modern solvers will do most of the complicated stuff for you.
This [primer](https://www.gurobi.com/resources/mixed-integer-programming-mip-a-primer-on-the-basics/) gives you some further details on how it is working.
We will focus here on just modelling the problems and using such a solver as a black box.
## Modelling
TBD: Give some basics on modelling.
Ein *lineares Programm* (LP) besteht, ähnlich wie ein Constraint Program, aus Variablen $x_1,\ldots,x_n$ sowie einer Zielfunktion und Nebenbedingungen über diesen Variablen.
Allerdings ist man bei einem LP auf eine lineare Zielfunktion sowie lineare Nebenbedingungen beschränkt.
Die Zielfunktion hat im Allgemeinen die Form $\sum_{i=1}^{n} c_ix_i$ für frei bestimmbare, aber feste Koeffizienten $c_i \in \mathbb{R}$, und kann entweder minimiert oder maximiert werden.
Es ist insbesondere nicht möglich, Variablen miteinander zu multiplizieren oder ihren Absolutwert zu bestimmen, oder ähnliche nicht-lineare Elemente in die Zielfunktion einzubauen.
Genau wie die Zielfunktion müssen auch die Nebenbedingungen linear sein.
Die $j$-te Nebenbedingung hat im Allgemeinen die Form $\sum_{i=1}^n a_{ij}x_i \leq b_j$, wobei $a_{ij}$ und $b_j$ wieder frei bestimmbare, aber feste Koeffizienten aus $\mathbb{R}$ sind.
Es darf statt $\leq$ auch $\geq$ oder $=$ genutzt werden.
Auch hier ist es also nicht möglich, Variablen miteinander zu multiplizieren oder andere nicht-lineare Nebenbedingungen einzuführen.
Üblicherweise haben die Variablen Schranken, also Nebenbedingungen der Form $\ell_i \leq x_i \leq u_i$, die ihren Wertebereich einschränken.
Diese kann man gesondert behandeln, aber auch einfach als zusätzliche Nebenbedingungen betrachten.
Nun brauchen wir zur Modellierung eines NP-schweren Problems natürlich zwingend ein NP-schweres Problem, es sei denn, uns ist der Beweis für $P = NP$ geglückt.
Hier kommt die Erweiterung der linearen Programmierung zur ganzzahligen linearen Programmierung (IP) ins Spiel.
Sie erlaubt es uns, alle Variablen (oder auch nur einen Teil der Variablen\footnote{Man spricht technisch gesehen von einem Mixed Integer Program (MIP), wenn es sowohl ganzzahlige als auch kontinuierliche Variablen gibt.}) auf ganzzahlige Werte zu beschränken.
Dadurch wird es zum einen möglich, unter anderem Entscheidungsvariablen als $\{0,1\}$-Variablen zu implementieren, und das Problem wird NP-schwer.
Zum anderen erlaubt uns Integer Programming, auf Umwegen auch einige nicht-lineare Nebenbedingungen zu modellieren.
Anders als die bisher verwendeten CP- und SAT-Solver bieten IP-Solver oft die Möglichkeit, während einer laufenden Suche neue Bedingungen hinzuzufügen.
Solche Nebenbedingungen werden auch *Lazy Constraints* genannt.
Dies geschieht in der Regel durch sogenannte \emph{Callbacks}: Während der Suche ruft der IP-Solver an bestimmten Punkten den zuvor registrierten Code des Benutzers auf.
Dabei erhält der Benutzer-Code Zugriff auf eine aktuelle (ganzzahlige oder auch nicht-ganzzahlige) Lösung und erhält die Möglichkeit, neue Bedingungen einzufügen, die diese Lösung verbieten.
Dies wird beim TSP zum Beispiel verwendet, um während der Lösung auftretende Subtouren zu verbieten, ähnlich wie wir das bei SAT gemacht haben, allerdings ohne den Solver immer wieder neu zu starten.
You can also check out these [video lectures](https://www.gurobi.com/resource/tutorial-mixed-integer-linear-programming/).
We again provide an [example for the Degree Constrained Bottleneck Spanning Tree](./dbst_mip).
## Installation
We will use Gurobi. In case you haven't already installed it, you can do so using Anaconda
Wie auch schon bei SAT und CP wollen wir wieder das Problem *Degree Constrained Bottleneck Spanning Tree* als ausführliches Beispiel benutzen.
Allerdings erweitern wir das Problem diesmal wie folgt:
Unter allen Spannbäumen mit minimalem Bottleneck wollen wir einen Spannbaum finden, der das Gesamtgewicht aller Kanten minimiert.
Der Code ist wieder in Form eines Jupyter-Notebooks beigelegt.
Bitte bedenkt, dass wir den Code nur für die Lesbarkeit direkt im Jupyter-Notebook haben, Ihr den Code aber besser in einer separaten Python-Datei schreibt, da diese besser gemeinsam bearbeitet und wiederverwendet werden kann.
## Ansatz für die Zielfunktion
Obwohl IPs genau wie CPs die Minimierung einer Zielfunktion direkt unterstützen, können wir unsere zweiteilige Zielfunktion nicht direkt in einem IP modellieren.
Es gibt nun verschiedene mögliche Ansätze.
Wir könnten zum einen wie bei SAT eine binäre Suche nach dem optimalen Bottleneck durchführen.
Wir können aber auch ein erstes IP benutzen, um nach dem optimalen Bottleneck zu suchen, und dann in einem zweiten Schritt nach dem günstigsten Spannbaum mit dem gegebenen Bottleneck suchen.
Wir werden im Folgenden die zweite Möglichkeit nutzen; wir verwenden zuvor aber die bereits bei SAT verwendete Greedy-Heuristik, um eine obere Schranke an das Bottleneck zu finden.
Dann können wir von vornherein alle Kanten weglassen, die länger als dieses Bottleneck sind.
Das hilft uns dabei, möglichst wenige mögliche Kanten in unserem IP zu haben, und hat hoffentlich einen spürbaren positiven Einfluss auf die Lösungszeit.
## Variablen und Nebenbedingungen
Wir führen für jede mögliche ungerichtete Kante $\{u,v\}$ eine Variable $x_{u,v} \in \mathbb{B}$ ein,
die genau dann den Wert 1 annehmen soll, wenn die Kante $\{u,v\}$ in unserem Spannbaum enthalten ist.
Die Gradbeschränkung auf einen maximalen Grad von $d$ können wir dann für jeden Knoten $v$ als
```equation
\sum\limits_{e \in \delta(\{v\})}x_e \leq d
```
realisieren.
Dabei ist $\delta(S)$ für eine Knotenmenge $S$ die Menge der Kanten, die einen Knoten $s \in S$ mit einem Knoten $t \in V \setminus S$ verbinden, also den ,,Rand'' der Menge $S$ überqueren.
Weiterhin muss
```equation
\sum\limits_{e \in \delta(\{v\})}x_e \geq 1
```
gelten, damit jeder Knoten wenigstens einen Nachbarn hat.
Wir fügen auch
```equation
\sum\limits_{e \in E}x_e = n-1
```
als Bedingung hinzu.
Um den Zusammenhang sicherzustellen, verwenden wir diesmal die Möglichkeit, Lazy Constraints während der Suche einzufügen.
Wir fügen, ähnlich wie wir es bei SAT getan haben, für jede Teilmenge $\emptyset \neq S \subsetneq V$ die Nebenbedingung
```equation
\sum\limits_{e \in \delta(S)} x_e \geq 1
```
hinzu.
Dies stellt sicher, dass wenigstens eine Kante die Knoten der Menge $S$ mit den restlichen Knoten verbindet.
Da es insgesamt $\Theta(2^n)$ mögliche Teilmengen $S$ gibt, können wir diese Nebenbedingungen nicht alle auf einmal hinzufügen;
stattdessen untersuchen wir die bei der Suche gefundenen Lösungen und fügen nur Nebenbedingungen hinzu, die in diesen Lösungen verletzt sind.
## Suche nach bestem Bottleneck
Wenn wir nach dem kleinsten möglichen Bottleneck suchen, führen wir die zusätzliche Variable $\ell$ ein, die die Länge der längsten Kante darstellen soll.
Die Zielfunktion ist in diesem Fall also $\min \ell$.
Um alle Kanten zu verbieten, die länger sind als $\ell$, verwenden wir für jede Kante $e$ die Nebenbedingung $\ell \geq c_ex_e$, wobei $c_e$ die Länge der Kante $e$ ist.
## Suche nach optimalem Spannbaum
Nachdem wir das optimale Bottleneck $\ell^*$ gefunden haben, suchen wir nach dem Baum mit den geringsten Kosten,
indem wir das Problem ohne die Zielfunktionsvariable $\ell$ und ihre Nebenbedingungen lösen.
Dabei haben wir nur noch für die Kanten $e$ mit $c_e \leq \ell^*$ eine Kantenvariable; alle anderen Kanten dürfen wir nicht verwenden.
Als Zielfunktion verwenden wir $\min \sum_e c_ex_e$, also die Summe der Länge aller gewählten Kanten.