Glossar-Links: Fachbegriffe im Fließtext automatisch verlinken

Auto-Linking im Build: Glossar-Begriffe werden im Fließtext automatisch
erkannt und als Link auf den Glossar-Eintrag gesetzt — erste Nennung je
Begriff je Datei, dezent gestrichelte Unterstreichung im HTML, hyperref
im PDF. Kein manuelles Markieren bei Textänderungen nötig.

Neue Infrastruktur:
- glossar_eintraege_04.py: glossar_slug() + LINK_TEXTE-Map (automatisch
  generiert aus EINTRAEGEN + Abkürzungs-Extraktion)
- erzeuge_glossar_04.py: jeder Eintrag bekommt {#gloss:<slug>}-Anchor
- build_version_04.py: resolve_glossar() — Auto-Linking + {gl:}-Marke
- web_04/site.css: .glossar-link (dezent, nicht wie Navigationslink)

5 neue Glossar-Einträge (waren als Begriff im Text, aber nicht im Glossar):
Solver, Scheduling, Graphen, Backtest, DCP (Disciplined Convex Programming)

Schutzmechanismen:
- Code-Blöcke, Inline-Code, {idx:}/{ref:}-Marken, Markdown-Links und
  Überschriften werden nicht verlinkt
- Verschachtelung verhindert: 'CP-SAT-Solver' als Ganzes, nicht 'CP'+[
  -'SAT'+[-'Solver'
- 'OR' als 2-Zeichen-Abkürzung nicht auto-verlinkt (False Positive in
  'OR-Tools'); {gl:OR} als manuelle Marke
- Glossar selbst nicht auto-verlinkt (sonst Self-Links)
- reflow_markdown(): ::: als Block-Grenze (sonst kollabiert fenced div)

619 Glossar-Links im Gesamtdokument, 239 Anchor im Glossar, 34 im Vorwort.
This commit is contained in:
dschlueter 2026-09-10 17:40:29 +02:00
commit e8abb66d75
52 changed files with 4316 additions and 1921 deletions

36
OR_HTML_04/cpsat.html generated
View file

@ -105,7 +105,7 @@
<p><strong>📌 Kapitel auf einen Blick</strong></p>
<p><strong>Worum geht es?</strong> Um ein völlig anderes Denkmodell: Statt algebraischer Ungleichungen arbeitet Constraint Programming mit <strong>Wertebereichen</strong> und <strong>logischen Regeln</strong>. Für Dienstpläne, Zuordnungen und Reihenfolgen ist das die mit Abstand produktivste Methode.</p>
<p><strong>Voraussetzungen:</strong> <a href="milp.html#kap-milp">Kapitel 6</a> (Binärvariablen, logische Bedingungen).</p>
<p><strong>Danach können Sie:</strong> Zuweisungs-, Scheduling- und Reihenfolgeprobleme mit CP-SAT modellieren, globale Constraints einsetzen, weiche Ziele über Strafkosten steuern — und alle fünf Solver-Antworten auseinanderhalten, statt nur auf <code>OPTIMAL</code> zu prüfen.</p>
<p><strong>Danach können Sie:</strong> Zuweisungs-, <a href="anhang-glossar.md#gloss:scheduling" class="glossar-link">Scheduling</a>- und Reihenfolgeprobleme mit <a href="anhang-glossar.md#gloss:cp-sat" class="glossar-link">CP-SAT</a> modellieren, globale Constraints einsetzen, weiche Ziele über <a href="anhang-glossar.md#gloss:strafkosten" class="glossar-link">Strafkosten</a> steuern — und alle fünf <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a>-Antworten auseinanderhalten, statt nur auf <code>OPTIMAL</code> zu prüfen.</p>
<p><strong>Zeitbedarf:</strong> ca. 6,5 Stunden.</p>
<p><strong>Programme:</strong><br />
<code>Propagation_Demo.py</code><br />
@ -151,7 +151,7 @@ Do -&gt; Bux
Fr -&gt; Abel</code></pre>
</blockquote>
</div>
<p><strong>Elf Zeilen Modell, und der Plan steht.</strong> Beachten Sie besonders die Zeile <code>m.AddExactlyOne(...)</code>. Sie sagt wörtlich, was gemeint ist: <em>genau eine</em> Person je Tag. In MILP hätte man dafür eine Gleichung <span class="math inline">\sum_p x_{p,d} = 1</span> aufgestellt — machbar, aber schon eine Übersetzungsleistung. Bei Regeln wie „diese beiden Dienste nicht direkt hintereinander“ oder „höchstens eine Nachtschicht pro Woche“ wächst dieser Übersetzungsaufwand rasch; in CP-SAT bleibt er eine Zeile.</p>
<p><strong>Elf Zeilen Modell, und der Plan steht.</strong> Beachten Sie besonders die Zeile <code>m.AddExactlyOne(...)</code>. Sie sagt wörtlich, was gemeint ist: <em>genau eine</em> Person je Tag. In <a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP</a> hätte man dafür eine Gleichung <span class="math inline">\sum_p x_{p,d} = 1</span> aufgestellt — machbar, aber schon eine Übersetzungsleistung. Bei Regeln wie „diese beiden Dienste nicht direkt hintereinander“ oder „höchstens eine Nachtschicht pro Woche“ wächst dieser Übersetzungsaufwand rasch; in <a href="anhang-glossar.md#gloss:constraint-programming-cp" class="glossar-link">CP</a>-<a href="anhang-glossar.md#gloss:sat-boolean-satisfiability" class="glossar-link">SAT</a> bleibt er eine Zeile.</p>
<p><strong>Und jetzt sehen Sie sich das Ergebnis genau an.</strong> <em>Cor bekommt keinen einzigen Dienst.</em> Dane arbeitet Montag <strong>und</strong> Dienstag hintereinander. Ist das ein Fehler?</p>
<p>Nein — und das ist die wichtigste Lektion dieses Kapitels. Der Plan erfüllt <strong>jede</strong> Regel, die wir aufgeschrieben haben. Wir haben nur nie nach Fairness gefragt, und wir haben nie gesagt, dass Dienste nicht aufeinanderfolgen sollen. Ein Solver liefert exakt das Bestellte — nicht das Gemeinte.</p>
<blockquote>
@ -162,17 +162,17 @@ Fr -&gt; Abel</code></pre>
<p>Nach diesem Kapitel können Sie …</p>
<ol type="1">
<li>… erklären, wodurch sich Constraint Programming von MILP unterscheidet.</li>
<li>… die Wirkung von <strong>Constraint Propagation</strong> an einem Beispiel nachvollziehen.</li>
<li>… die Wirkung von <strong><a href="anhang-glossar.md#gloss:constraint-propagation" class="glossar-link">Constraint Propagation</a></strong> an einem Beispiel nachvollziehen.</li>
<li>… die wichtigsten globalen Constraints einsetzen: <code>AddExactlyOne</code>, <code>AddAllDifferent</code>, <code>AddNoOverlap</code>, <code>AddCumulative</code>.</li>
<li>… mit <strong>Intervallvariablen</strong> ein Maschinenbelegungsproblem modellieren.</li>
<li>… harte Regeln und weiche Ziele in einem Modell kombinieren und gewichten.</li>
<li>… die fünf CP-SAT-Statusfälle unterscheiden — insbesondere <code>INFEASIBLE</code> (es gibt keine Lösung) von <code>UNKNOWN</code> (es wurde keine gefunden) und <code>MODEL_INVALID</code> (Ihr Code ist fehlerhaft).</li>
<li>… begründen, warum <code>num_workers = 1</code> für einen reproduzierbaren Lauf nötig ist und ein fester <code>random_seed</code> allein nicht genügt — und was ein Test deshalb prüfen darf.</li>
<li>… begründen, warum ein Strafgewicht ein <strong>Wechselkurs</strong> ist, und welche Regeln deshalb niemals in die Zielfunktion gehören.</li>
<li>… begründen, warum ein Strafgewicht ein <strong>Wechselkurs</strong> ist, und welche Regeln deshalb niemals in die <a href="anhang-glossar.md#gloss:zielfunktion" class="glossar-link">Zielfunktion</a> gehören.</li>
</ol>
<hr />
<h2 id="sec:cpsat-ein-anderes-denkmodell">7.3 Ein anderes Denkmodell</h2>
<p>Während LP und MILP auf algebraischen Gleichungen und Simplex-Matrizen beruhen, kommt <strong>Constraint Programming (CP)</strong> aus der künstlichen Intelligenz:</p>
<p>Während <a href="anhang-glossar.md#gloss:lineare-programmierung-lp" class="glossar-link">LP</a> und MILP auf algebraischen Gleichungen und Simplex-Matrizen beruhen, kommt <strong>Constraint Programming (CP)</strong> aus der künstlichen Intelligenz:</p>
<table>
<colgroup>
<col style="width: 33%" />
@ -199,7 +199,7 @@ Fr -&gt; Abel</code></pre>
</tr>
<tr class="odd">
<td>Suchprinzip</td>
<td>Relaxation + Verzweigen + Schranken</td>
<td><a href="anhang-glossar.md#gloss:relaxation" class="glossar-link">Relaxation</a> + Verzweigen + Schranken</td>
<td><strong>Propagation</strong> + Verzweigen + Konfliktlernen</td>
</tr>
<tr class="even">
@ -214,7 +214,7 @@ Fr -&gt; Abel</code></pre>
</tr>
</tbody>
</table>
<p>Der <strong>CP-SAT-Solver</strong> von Google OR-Tools verbindet Constraint Programming mit modernen <strong>SAT-Techniken</strong> (<em>Boolean Satisfiability</em>, Erfüllbarkeitsproblem der Aussagenlogik), insbesondere <strong>CDCL</strong> (<em>Conflict-Driven Clause Learning</em>, konfliktgetriebenes Klausellernen). Er gehört zu den weltweit leistungsfähigsten Engines für Schichtpläne, Vertretungsplanung, Projektterminierung und Job-Shop-Scheduling.</p>
<p>Der <strong><a href="anhang-glossar.md#gloss:cp-sat-solver" class="glossar-link">CP-SAT-Solver</a></strong> von Google <a href="anhang-glossar.md#gloss:or-tools" class="glossar-link">OR-Tools</a> verbindet Constraint Programming mit modernen <strong>SAT-Techniken</strong> (<em>Boolean Satisfiability</em>, Erfüllbarkeitsproblem der Aussagenlogik), insbesondere <strong><a href="anhang-glossar.md#gloss:cdcl" class="glossar-link">CDCL</a></strong> (<em>Conflict-Driven Clause Learning</em>, konfliktgetriebenes Klausellernen). Er gehört zu den weltweit leistungsfähigsten Engines für Schichtpläne, Vertretungsplanung, Projektterminierung und <a href="anhang-glossar.md#gloss:job-shop-scheduling" class="glossar-link">Job-Shop-Scheduling</a>.</p>
<h3 id="was-propagation-leistet">Was Propagation leistet</h3>
<figure>
<img src="bilder_04/kap06_cp_sat_propagation.svg" alt="Abb. 7.1: CP-SAT Propagation und Conflict Learning" />
@ -365,7 +365,7 @@ Fr -&gt; Abel</code></pre>
<tr class="odd">
<td><code>AddCircuit(arcs)</code></td>
<td>Die gewählten Kanten bilden <strong>einen</strong> Rundweg</td>
<td>TSP-artige Reihenfolgeprobleme</td>
<td><a href="anhang-glossar.md#gloss:tsp-traveling-salesperson-problem" class="glossar-link">TSP</a>-artige Reihenfolgeprobleme</td>
</tr>
<tr class="even">
<td><code>.OnlyEnforceIf(bool)</code></td>
@ -377,7 +377,7 @@ Fr -&gt; Abel</code></pre>
<div class="card card-formel">
<blockquote>
<p><strong>🔤 Formel-Übersetzer: was ein globales Constraint zusichert</strong></p>
<p>Globale Constraints tragen englische Methodennamen, meinen aber alltägliche Regeln. Links die mathematische Zusicherung, rechts der Satz, den Sie im Planungsgespräch sagen würden:</p>
<p><a href="anhang-glossar.md#gloss:globale-constraints" class="glossar-link">Globale Constraints</a> tragen englische Methodennamen, meinen aber alltägliche Regeln. Links die mathematische Zusicherung, rechts der Satz, den Sie im Planungsgespräch sagen würden:</p>
<table>
<colgroup>
<col style="width: 50%" />
@ -424,7 +424,7 @@ Fr -&gt; Abel</code></pre>
</blockquote>
</div>
<blockquote>
<p><strong>💡 Warum globale Constraints so viel bringen</strong> <code>AddAllDifferent</code> über 9 Variablen ersetzt <span class="math inline">\binom{9}{2} = 36</span> paarweise Ungleichheitsbedingungen — und propagiert <strong>stärker</strong> als die 36 Einzelbedingungen zusammen, weil der Propagator die Struktur als Ganzes betrachtet (er nutzt Matching-Theorie auf bipartiten Graphen). Ein MILP-Modell derselben Regel bräuchte Big-M-Konstruktionen und würde deutlich langsamer laufen.</p>
<p><strong>💡 Warum globale Constraints so viel bringen</strong> <code>AddAllDifferent</code> über 9 Variablen ersetzt <span class="math inline">\binom{9}{2} = 36</span> paarweise Ungleichheitsbedingungen — und propagiert <strong>stärker</strong> als die 36 Einzelbedingungen zusammen, weil der Propagator die Struktur als Ganzes betrachtet (er nutzt Matching-Theorie auf bipartiten <a href="anhang-glossar.md#gloss:graphen" class="glossar-link">Graphen</a>). Ein MILP-Modell derselben Regel bräuchte Big-M-Konstruktionen und würde deutlich langsamer laufen.</p>
</blockquote>
<hr />
<h2 id="sec:cpsat-praxisfall-dynamisches-vertretungssystem">7.5 Praxisfall: Dynamisches Vertretungssystem</h2>
@ -724,10 +724,10 @@ Suchstatistik: 113 Verzweigungen, 3 Konflikte, 0.006 s
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>modell.AddBoolOr([x[p,s].Not(), x[p,s<span class="op">+</span><span class="dv">1</span>], x[p,s<span class="op">+</span><span class="dv">2</span>].Not()]).OnlyEnforceIf(loch.Not())</span></code></pre></div>
<p>koppeln die Hilfsvariable <code>loch</code> <strong>in beide Richtungen</strong> an das Muster „arbeitetfreiarbeitet“. Die erste Zeile erzwingt: <em>wenn</em> <code>loch = 1</code>, <em>dann</em> liegt das Muster vor. Die zweite: <em>wenn</em> <code>loch = 0</code>, <em>dann</em> liegt es nicht vor. In diesem Modell genügte streng genommen die erste Zeile, weil <code>loch</code> nur mit positiven Strafkosten vorkommt und der Minimierer es daher von selbst auf 0 drückt — die zweite Zeile macht das Modell aber robust gegen spätere Änderungen und ist selbstdokumentierend.</p>
<p><strong>2. Fairness über Spannweite (W3).</strong> <code>AddMaxEquality</code> und <code>AddMinEquality</code> sind CP-SAT-Konstrukte, die in MILP mehrere Big-M-Ungleichungen bräuchten. Die Spannweite <span class="math inline">\max - \min</span> zu minimieren ist ein Standardrezept für „gleichmäßig verteilen“.</p>
<p><strong>3. Kostenzerlegung.</strong> Der Block „Woraus bestehen die Strafkosten?“ ist kein Beiwerk. In der Praxis ist die erste Frage jedes Anwenders: <em>„Warum bekomme <strong>ich</strong> die Stunde?“</em> Wer darauf keine Antwort hat, dessen System wird nicht benutzt (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>, <em>Explainable OR</em>).</p>
<p><strong>3. <a href="anhang-glossar.md#gloss:kostenzerlegung" class="glossar-link">Kostenzerlegung</a>.</strong> Der Block „Woraus bestehen die Strafkosten?“ ist kein Beiwerk. In der Praxis ist die erste Frage jedes Anwenders: <em>„Warum bekomme <strong>ich</strong> die Stunde?“</em> Wer darauf keine Antwort hat, dessen System wird nicht benutzt (<a href="praxisfallen.html#kap-praxisfallen">Kapitel 22</a>, <em><a href="anhang-glossar.md#gloss:explainable-or" class="glossar-link">Explainable OR</a></em>).</p>
<p><strong>4. Statusauswertung ohne CP-SAT-Vokabular.</strong> Nach <code>Solve()</code> steht nur eine einzige Zeile mit einer CP-SAT-Konstante darin:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>status <span class="op">=</span> status_von_cpsat(rohstatus)</span></code></pre></div>
<p>Danach heißen die Fälle <code>status.modellfehler</code> (die Regeln widersprechen sich — das Modell muss geändert werden) und <code>status.brauchbar</code> (es liegt etwas Ausführbares vor). Das ist <strong>dieselbe</strong> Fallunterscheidung wie bei HiGHS in <a href="milp.html#kap-milp">Kapitel 6</a> und bei GLOP in <a href="lp.html#kap-lp">Kapitel 5</a>; nur die eine Übersetzerzeile weiß, welcher Solver gerechnet hat. Wer den Auswertungscode direkt gegen <code>cp_model.INFEASIBLE</code> schreibt, kann ihn bei einem Solverwechsel wegwerfen — <a href="praxisfallen.html#sec:praxisfallen-solverwechsel">Abschnitt 22.7</a> führt genau diesen Wechsel vor.</p>
<p>Danach heißen die Fälle <code>status.modellfehler</code> (die Regeln widersprechen sich — das Modell muss geändert werden) und <code>status.brauchbar</code> (es liegt etwas Ausführbares vor). Das ist <strong>dieselbe</strong> Fallunterscheidung wie bei <a href="anhang-glossar.md#gloss:highs" class="glossar-link">HiGHS</a> in <a href="milp.html#kap-milp">Kapitel 6</a> und bei <a href="anhang-glossar.md#gloss:glop" class="glossar-link">GLOP</a> in <a href="lp.html#kap-lp">Kapitel 5</a>; nur die eine Übersetzerzeile weiß, welcher Solver gerechnet hat. Wer den Auswertungscode direkt gegen <code>cp_model.INFEASIBLE</code> schreibt, kann ihn bei einem Solverwechsel wegwerfen — <a href="praxisfallen.html#sec:praxisfallen-solverwechsel">Abschnitt 22.7</a> führt genau diesen Wechsel vor.</p>
</blockquote>
<blockquote>
<p><strong>⚠️ Typische Fehler</strong></p>
@ -740,7 +740,7 @@ Suchstatistik: 113 Verzweigungen, 3 Konflikte, 0.006 s
<hr />
<h2 id="sec:cpsat-intervallvariablen-job-shop-scheduling">7.6 Intervallvariablen: Job-Shop-Scheduling</h2>
<p>Die Königsdisziplin von CP-SAT sind Zeitplanungsprobleme. Intervallvariablen sind dabei das zentrale Werkzeug.</p>
<p><strong>Problem.</strong> Drei Aufträge, je drei Arbeitsgänge auf drei Maschinen. Jeder Auftrag muss seine Gänge in fester Reihenfolge durchlaufen; jede Maschine kann nur einen Gang gleichzeitig bearbeiten. Gesucht: der Plan mit der kürzesten Gesamtdauer (<em>Makespan</em>).</p>
<p><strong>Problem.</strong> Drei Aufträge, je drei Arbeitsgänge auf drei Maschinen. Jeder Auftrag muss seine Gänge in fester Reihenfolge durchlaufen; jede Maschine kann nur einen Gang gleichzeitig bearbeiten. Gesucht: der Plan mit der kürzesten Gesamtdauer (<em><a href="anhang-glossar.md#gloss:makespan" class="glossar-link">Makespan</a></em>).</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co">#!/usr/bin/env python3</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="co"># JobShop_Intervalle.py</span></span>
@ -1154,12 +1154,12 @@ random_seed = 1 in JEDEM Lauf, 4 Wiederholungen je Arbeiterzahl.
deshalb eine Entscheidung fuer den Test, nicht fuer den Betrieb.
==============================================================================</code></pre>
<h3 id="was-die-messung-zeigt">Was die Messung zeigt</h3>
<p><strong>Erstens: Die Beschleunigung ist überlinear.</strong> Acht Arbeiter sind im abgedruckten Lauf nicht achtmal, sondern <strong>gut zwölfmal</strong> schneller als einer. Das sieht nach Messfehler aus, ist aber die korrekte Beschreibung dessen, was CP-SAT tut: Es lässt <strong>nicht</strong> achtmal dieselbe Suche auf verschiedenen Kernen laufen, sondern <strong>verschiedene Strategien nebeneinander</strong> — Suche mit LP-Relaxation, Suche mit anderen Verzweigungsregeln, lokale Suche —, und die teilen einander ihre Schranken mit. Der zweite Arbeiter ist deshalb nicht „der zweite Kern“, sondern ein anderes Verfahren, das zufällig auch rechnet.</p>
<p><strong>Erstens: Die Beschleunigung ist überlinear.</strong> Acht Arbeiter sind im abgedruckten Lauf nicht achtmal, sondern <strong>gut zwölfmal</strong> schneller als einer. Das sieht nach Messfehler aus, ist aber die korrekte Beschreibung dessen, was CP-SAT tut: Es lässt <strong>nicht</strong> achtmal dieselbe Suche auf verschiedenen Kernen laufen, sondern <strong>verschiedene Strategien nebeneinander</strong> — Suche mit <a href="anhang-glossar.md#gloss:lp-relaxation" class="glossar-link">LP-Relaxation</a>, Suche mit anderen Verzweigungsregeln, lokale Suche —, und die teilen einander ihre Schranken mit. Der zweite Arbeiter ist deshalb nicht „der zweite Kern“, sondern ein anderes Verfahren, das zufällig auch rechnet.</p>
<p>Daraus folgt zweierlei. Ein Speedup über der Kernzahl ist bei CP-SAT normal und kein Grund zum Misstrauen. Und der Zugewinn flacht ab: Zwischen vier und acht Arbeitern lag in unseren Wiederholungen mal das eine, mal das andere vorn — ab einer gewissen Zahl kosten Abstimmung und Speicherbandbreite mehr, als ein weiterer Suchstrang einbringt. Auch hier gilt: messen, nicht schätzen.</p>
<p><strong>Zweitens — und das ist der Befund, der die Testpraxis bestimmt: Der Seed genügt nicht.</strong> Bei einem Arbeiter liefern vier Läufe <strong>einen</strong> Plan. Schon bei <strong>zwei</strong> Arbeitern liefern vier Läufe <strong>drei verschiedene</strong> — bei identischem <code>random_seed</code> und identischem Zielwert 183.</p>
<p>Es braucht also keine acht Arbeiter, um die Reproduzierbarkeit zu verlieren. Zwei genügen. Der Grund ist einfach, sobald man ihn ausspricht: Der Seed legt fest, wie ein <strong>einzelner</strong> Suchstrang würfelt. Er legt nicht fest, <strong>welcher von mehreren zuerst fertig wird</strong> — das entscheidet die Uhr, und die ist bei jedem Lauf anders.</p>
<blockquote>
<p><strong>⚠️ <code>random_seed</code> allein macht nichts reproduzierbar</strong> Ein häufiger Irrtum: „Ich habe doch einen Seed gesetzt.“ Für einen Zufallsgenerator stimmt das. Für einen <strong>parallelen</strong> Solver nicht — dort ist die zweite Zufallsquelle die Ausführungsreihenfolge, und die lässt sich nicht mit einem Parameter festnageln. Reproduzierbar wird der Lauf erst mit <code>num_workers = 1</code> <strong>und</strong> Seed. Wer nur eines von beidem setzt, hat einen Test, der gelegentlich grundlos rot wird.</p>
<p><strong>⚠️ <code>random_seed</code> allein macht nichts reproduzierbar</strong> Ein häufiger Irrtum: „Ich habe doch einen Seed gesetzt.“ Für einen Zufallsgenerator stimmt das. Für einen <strong>parallelen</strong> Solver nicht — dort ist die zweite Zufallsquelle die Ausführungsreihenfolge, und die lässt sich nicht mit einem <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a> festnageln. Reproduzierbar wird der Lauf erst mit <code>num_workers = 1</code> <strong>und</strong> Seed. Wer nur eines von beidem setzt, hat einen Test, der gelegentlich grundlos rot wird.</p>
</blockquote>
<h3 id="was-daraus-für-tests-folgt">Was daraus für Tests folgt</h3>
<p>Der Zielwert war in <strong>allen</strong> sechzehn Läufen derselbe: 183. Die Gestalt der Lösung war es nicht. Genau daran entscheidet sich, was ein Test prüfen darf:</p>
@ -1576,7 +1576,7 @@ genau die Information, die zur Fehlersuche noetig waere.
<p><strong>Aufgabe 7.1 ⭐ — Propagation nachvollziehen.</strong> <span class="math inline">x_1 \in \{1,\dots,6\}</span>, <span class="math inline">x_2 \in \{1,\dots,6\}</span>, Bedingungen: <span class="math inline">x_1 &lt; x_2</span> und <span class="math inline">x_1 + x_2 = 8</span>. Reduzieren Sie beide Wertebereiche von Hand so weit wie möglich.</p>
<p><strong>Aufgabe 7.2 ⭐ — Hart oder weich?</strong> Klassifizieren Sie und begründen Sie, welche Strafhöhe Sie ansetzen würden: (a) Gesetzliche Mindestruhezeit. (b) „Herr Bauer möchte montags frei.“ (c) „In jeder Schicht mindestens eine examinierte Kraft.“ (d) „Möglichst keine geteilten Dienste.“ (e) „Kein Dienst länger als 10 Stunden.“</p>
<p><strong>Aufgabe 7.3 ⭐⭐ — Regeln ergänzen.</strong> Erweitern Sie das Vertretungssystem um: (a) Frau Albrecht ist ab Slot 3 nicht mehr verfügbar. (b) Herr Bauer und Frau Koch sollen nicht beide am selben Tag eingesetzt werden. (c) Wer Slot 1 übernimmt, soll bevorzugt auch Slot 2 bekommen (Belohnung statt Strafe).</p>
<p><strong>Aufgabe 7.4 ⭐⭐ — Infeasibility erzeugen und diagnostizieren.</strong> Setzen Sie <code>MAX_VERTRETUNGEN = 0</code>. Was meldet das Programm? Bauen Sie anschließend eine <strong>Schlupfvariable</strong> je Slot ein („Stunde fällt aus“, Strafe 10 000) und beobachten Sie, wie der Solver statt einer Fehlermeldung einen Notfallplan liefert. Welche Stunde lässt er ausfallen und warum?</p>
<p><strong>Aufgabe 7.4 ⭐⭐ — <a href="anhang-glossar.md#gloss:infeasibility" class="glossar-link">Infeasibility</a> erzeugen und diagnostizieren.</strong> Setzen Sie <code>MAX_VERTRETUNGEN = 0</code>. Was meldet das Programm? Bauen Sie anschließend eine <strong>Schlupfvariable</strong> je Slot ein („Stunde fällt aus“, Strafe 10 000) und beobachten Sie, wie der Solver statt einer Fehlermeldung einen Notfallplan liefert. Welche Stunde lässt er ausfallen und warum?</p>
<p><strong>Aufgabe 7.5 ⭐⭐ — Sudoku.</strong> Lösen Sie ein 9×9-Sudoku mit CP-SAT. Sie brauchen nur <code>NewIntVar(1, 9, ...)</code> und <code>AddAllDifferent</code> für Zeilen, Spalten und die neun 3×3-Blöcke. Wie viele Zeilen Modellcode benötigen Sie?</p>
<p><strong>Aufgabe 7.6 ⭐⭐ — Die Grenze der Parallelität finden.</strong> <code>Parallele_Suche.py</code> misst 1, 2, 4 und 8 Arbeiter. (a) Ergänzen Sie 16 und 24 (bzw. die Kernzahl Ihres Rechners). Ab wo bringt ein weiterer Arbeiter nichts mehr — und wird es irgendwann wieder schlechter? (b) Setzen Sie <code>random_seed</code> bei jedem Lauf auf einen <strong>anderen</strong> Wert, aber <code>num_workers = 1</code>. Wie viele verschiedene Pläne kommen jetzt heraus? Was folgt daraus für die Frage, welcher der beiden Parameter die Reproduzierbarkeit sichert? (c) Vergrößern Sie die Instanz auf 15 Aufträge. Ab welcher Größe erreicht der Lauf mit einem Arbeiter das Zeitlimit, während acht noch <code>OPTIMAL</code> melden?</p>
<p><strong>Aufgabe 7.7 ⭐⭐⭐ — Job-Shop erweitern.</strong> Ergänzen Sie <code>JobShop_Intervalle.py</code> um: (a) Rüstzeiten von 1 Zeiteinheit zwischen zwei Aufträgen auf derselben Maschine. (b) Fälligkeitstermine je Auftrag mit Strafkosten für Verspätung. (c) Eine zweite Fräse (Kapazität 2 statt 1) mit <code>AddCumulative</code>.</p>
@ -1777,7 +1777,7 @@ Nebenbedingungen. Alles, was einen Preis hat, wird irgendwann gekauft.
<ul>
<li><strong>Gewichte nach Bauchgefühl setzen.</strong> Rechnen Sie stattdessen die Tabelle wie oben und lassen Sie den Auftraggeber die Zeile aussuchen. Das dauert eine Stunde und ersetzt drei Abstimmungsrunden.</li>
<li><strong>Eine unverhandelbare Regel weich modellieren.</strong> Alles, was einen Preis hat, wird irgendwann gekauft. Gesetzliche Ruhezeiten, Qualifikationsanforderungen und Betriebsvereinbarungen gehören unter die <strong>harten</strong> Nebenbedingungen — auch wenn das Modell dann <code>INFEASIBLE</code> melden kann. Dieses <code>INFEASIBLE</code> ist die ehrliche Antwort: Der Plan ist mit dem vorhandenen Personal nicht zulässig zu machen.</li>
<li><strong>Gewichte in verschiedenen Einheiten mischen.</strong> Euro neben „Unzufriedenheitspunkten“ neben Minuten ergibt eine Zielfunktion, die niemand interpretieren kann — und deren Konditionszahl obendrein leidet (<a href="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>).</li>
<li><strong>Gewichte in verschiedenen Einheiten mischen.</strong> Euro neben „Unzufriedenheitspunkten“ neben Minuten ergibt eine Zielfunktion, die niemand interpretieren kann — und deren <a href="anhang-glossar.md#gloss:konditionszahl" class="glossar-link">Konditionszahl</a> obendrein leidet (<a href="fundament.html#sec:fundament-kondition">Abschnitt 2.7</a>).</li>
<li><strong>Nur ein Gewicht ausprobieren.</strong> Ein einziger Lauf sagt nichts darüber, ob Sie nahe an einem Kipppunkt stehen. Zwischen den Gewichten 5 und 10 ändert sich der Plan oben erheblich.</li>
</ul>
</blockquote>
@ -1810,7 +1810,7 @@ Nebenbedingungen. Alles, was einen Preis hat, wird irgendwann gekauft.
<h2 id="sec:cpsat-zusammenfassung">7.13 Zusammenfassung</h2>
<ul>
<li><p><strong>CP denkt in Wertebereichen und Regeln</strong>, nicht in Ungleichungen und Schranken.</p></li>
<li><p><strong>Propagation</strong> streicht unmögliche Werte, bevor gesucht wird; <strong>Conflict Learning</strong> verhindert, dieselbe Sackgasse zweimal zu betreten.</p></li>
<li><p><strong>Propagation</strong> streicht unmögliche Werte, bevor gesucht wird; <strong><a href="anhang-glossar.md#gloss:conflict-learning" class="glossar-link">Conflict Learning</a></strong> verhindert, dieselbe Sackgasse zweimal zu betreten.</p></li>
<li><p><strong>Globale Constraints</strong> wie , und ersetzen ganze Blöcke von Big-M-Ungleichungen und propagieren stärker.</p></li>
<li><p><strong>Intervallvariablen</strong> sind das Werkzeug für Zeitplanung; <code>AddNoOverlap</code> erledigt in einer Zeile, was in MILP dutzende Disjunktionen kostet.</p></li>
<li><p><strong>Weiche Ziele mit Strafkosten</strong> machen Modelle robust gegen <code>INFEASIBLE</code> — und die <strong>Kostenzerlegung</strong> macht sie erklärbar.</p></li>