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:
parent
81ddb02d5c
commit
e8abb66d75
52 changed files with 4316 additions and 1921 deletions
20
OR_HTML_04/testing.html
generated
20
OR_HTML_04/testing.html
generated
|
|
@ -120,9 +120,9 @@
|
|||
<div class="card card-schnellstart">
|
||||
<blockquote>
|
||||
<p><strong>🚀 In 5 Minuten gelöst: drei Zeilen, die einen echten Fehler fangen</strong></p>
|
||||
<p>Die Schreinerei aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>, nur mit etwas knapperen Kapazitäten. Der Solver liefert einen Plan, halbe Tische gibt es nicht — also wird gerundet.</p>
|
||||
<p>Die Schreinerei aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a>, nur mit etwas knapperen Kapazitäten. Der <a href="anhang-glossar.md#gloss:solver" class="glossar-link">Solver</a> liefert einen Plan, halbe Tische gibt es nicht — also wird gerundet.</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> numpy <span class="im">as</span> np</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> linprog</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> scipy.optimize <span class="im">import</span> [linprog](anhang<span class="op">-</span>glossar.md<span class="co">#gloss:linprog){.glossar-link}</span></span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>verbrauch_je_stueck <span class="op">=</span> np.array([[<span class="fl">3.0</span>, <span class="fl">1.0</span>], <span class="co"># Montagestunden je Tisch/Stuhl</span></span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> [<span class="fl">6.0</span>, <span class="fl">1.0</span>]]) <span class="co"># Plattenmaterial je Tisch/Stuhl</span></span>
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
AssertionError: Plan [31. 48.] braucht [141. 234.], verfuegbar sind [140. 232.]</code></pre>
|
||||
</blockquote>
|
||||
</div>
|
||||
<p><strong>Und jetzt der Punkt.</strong> Der Solver hat richtig gerechnet: 30,67 Tische und 48 Stühle sind das Optimum des LP. Das Runden hat den Plan kaputt gemacht — 31 Tische brauchen eine Montagestunde mehr, als es gibt. Ohne die drei Zeilen wäre dieser Plan in die Fertigung gegangen, und dort hätte am Monatsende eine Stunde gefehlt, die niemand einer Zeile Python zugeordnet hätte.</p>
|
||||
<p><strong>Und jetzt der Punkt.</strong> Der Solver hat richtig gerechnet: 30,67 Tische und 48 Stühle sind das Optimum des <a href="anhang-glossar.md#gloss:lineare-programmierung-lp" class="glossar-link">LP</a>. Das Runden hat den Plan kaputt gemacht — 31 Tische brauchen eine Montagestunde mehr, als es gibt. Ohne die drei Zeilen wäre dieser Plan in die Fertigung gegangen, und dort hätte am Monatsende eine Stunde gefehlt, die niemand einer Zeile Python zugeordnet hätte.</p>
|
||||
<p>Bemerkenswert ist, <strong>was</strong> der Test prüft. Er prüft nicht, ob 30,67 das Optimum ist — das könnte er gar nicht, dafür bräuchte er einen zweiten Solver. Er prüft die Eigenschaft, die jede brauchbare Lösung haben muss: <em>Sie hält die Kapazität ein.</em> Diese Verschiebung — <strong>von der Frage nach dem richtigen Wert zur Frage nach den notwendigen Eigenschaften</strong> — ist der Schlüssel zum ganzen Kapitel.</p>
|
||||
<blockquote>
|
||||
<p><strong>🎯 Merksatz</strong> Ein Optimierungsmodell lässt sich fast nie gegen den richtigen Wert testen. Gegen seine Eigenschaften lässt es sich immer testen.</p>
|
||||
|
|
@ -536,7 +536,7 @@ AssertionError: Plan [31. 48.] braucht [141. 234.], verfuegbar sind [140. 232.]<
|
|||
<tbody>
|
||||
<tr class="odd">
|
||||
<td><code>@pytest.fixture(params=MODELLBAUER, ids=NAMEN)</code></td>
|
||||
<td>Jeder Test läuft <strong>zweimal</strong> — einmal mit GLOP, einmal mit SciPy. Ein Test, der nur einen Solver sieht, prüft die Bibliothek mit statt das Modell</td>
|
||||
<td>Jeder Test läuft <strong>zweimal</strong> — einmal mit <a href="anhang-glossar.md#gloss:glop" class="glossar-link">GLOP</a>, einmal mit <a href="anhang-glossar.md#gloss:scipy" class="glossar-link">SciPy</a>. Ein Test, der nur einen Solver sieht, prüft die Bibliothek mit statt das Modell</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td><code>test_beide_solver_liefern_dasselbe</code></td>
|
||||
|
|
@ -1127,11 +1127,11 @@ NICHT ablesen darf man:
|
|||
</tbody>
|
||||
</table>
|
||||
<p><em>(Die genauen Prozentwerte schwanken von Lauf zu Lauf — es sind Verhältnisse zweier Zeitmessungen. Die Größenordnungen sind stabil.)</em></p>
|
||||
<p>Bei OR-Tools gehen drei von vier Sekunden dafür drauf, in Python 10 000 Variablenobjekte anzulegen und Nebenbedingungen daraus zusammenzusetzen. Wer an dieser Stelle einen schnelleren <em>Solver</em> sucht, sucht in der falschen Hälfte — die Antwort ist ein vektorisierter Modellaufbau (<a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>).</p>
|
||||
<p>Die zweite unbequeme Zahl steht ganz rechts: <strong>CVXPY braucht rund 240 MB</strong>, unabhängig von der Problemgröße — das ist der Preis seiner Modellierungsschicht. Auf einem Rechner mit knappem Speicher entscheidet diese Spalte und nicht die Laufzeit darüber, was überhaupt läuft.</p>
|
||||
<p>Bei <a href="anhang-glossar.md#gloss:or-tools" class="glossar-link">OR-Tools</a> gehen drei von vier Sekunden dafür drauf, in Python 10 000 Variablenobjekte anzulegen und Nebenbedingungen daraus zusammenzusetzen. Wer an dieser Stelle einen schnelleren <em>Solver</em> sucht, sucht in der falschen Hälfte — die Antwort ist ein vektorisierter Modellaufbau (<a href="oekosystem.html#kap-oekosystem">Kapitel 3</a>).</p>
|
||||
<p>Die zweite unbequeme Zahl steht ganz rechts: <strong><a href="anhang-glossar.md#gloss:cvxpy" class="glossar-link">CVXPY</a> braucht rund 240 MB</strong>, unabhängig von der Problemgröße — das ist der Preis seiner Modellierungsschicht. Auf einem Rechner mit knappem Speicher entscheidet diese Spalte und nicht die Laufzeit darüber, was überhaupt läuft.</p>
|
||||
<blockquote>
|
||||
<p><strong>⚠️ Was diese Tabelle nicht sagt</strong></p>
|
||||
<p>Sie sagt <strong>nicht</strong> „ortools ist langsamer als scipy”. Gemessen wurde <em>ein</em> Problemtyp in <em>einer</em> Formulierung auf <em>einer</em> Maschine. Ein MILP statt eines LP, eine andere Modellierung desselben Problems oder ein anderer Rechner können die Reihenfolge umdrehen — und bei MILPs tun sie es regelmäßig.</p>
|
||||
<p>Sie sagt <strong>nicht</strong> „ortools ist langsamer als scipy”. Gemessen wurde <em>ein</em> Problemtyp in <em>einer</em> Formulierung auf <em>einer</em> Maschine. Ein <a href="anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class="glossar-link">MILP</a> statt eines LP, eine andere Modellierung desselben Problems oder ein anderer Rechner können die Reihenfolge umdrehen — und bei MILPs tun sie es regelmäßig.</p>
|
||||
<p>Übertragbar ist der <strong>Trend</strong>, nicht der Absolutwert: dass der Aufbauanteil mit der Größe wächst, dass CVXPY einen konstanten Speichersockel hat. Der Zweck des Programms ist, dass Sie es auf Ihrer Maschine mit Ihrem Problem laufen lassen.</p>
|
||||
</blockquote>
|
||||
<hr />
|
||||
|
|
@ -1560,12 +1560,12 @@ Das zugehoerige Dockerfile ist kurz genug, um es ganz zu zeigen:
|
|||
================================================================================</code></pre>
|
||||
<blockquote>
|
||||
<p><strong>💻 Code-Durchgang: drei Entscheidungen</strong></p>
|
||||
<p><strong>1. Das Domänenmodell ist das API-Schema.</strong> Der Parameter heißt <code>problem: Produktionsproblem</code> — mehr steht nicht da. FastAPI validiert die Anfrage damit gegen dieselben Pydantic-Regeln wie der Excel-Import aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a> und antwortet bei Unsinn mit <code>422</code>, <strong>bevor</strong> ein Solver startet. Die drei abgewiesenen Anfragen im Selbsttest sind genau die drei Fälle, die <code>or_kern.py</code> schon beim Einlesen abfängt.</p>
|
||||
<p><strong>2. Die Antwort enthält Status und Gap, nicht nur Zahlen.</strong> Ein Aufrufer, der nur die Mengen bekommt, kann nicht unterscheiden, ob er ein bewiesenes Optimum oder einen Zeitlimit-Abbruch in der Hand hält. Das <code>Loesung</code>-Objekt geht deshalb vollständig durch die Schnittstelle — dasselbe DTO, das in <a href="praxisfallen.html#sec:praxisfallen-solverwechsel">Abschnitt 22.7</a> schon eine Prozessgrenze überlebt hat.</p>
|
||||
<p><strong>1. Das Domänenmodell ist das API-Schema.</strong> Der <a href="anhang-glossar.md#gloss:parameter" class="glossar-link">Parameter</a> heißt <code>problem: Produktionsproblem</code> — mehr steht nicht da. FastAPI validiert die Anfrage damit gegen dieselben Pydantic-Regeln wie der Excel-Import aus <a href="einfuehrung.html#kap-einfuehrung">Kapitel 1</a> und antwortet bei Unsinn mit <code>422</code>, <strong>bevor</strong> ein Solver startet. Die drei abgewiesenen Anfragen im Selbsttest sind genau die drei Fälle, die <code>or_kern.py</code> schon beim Einlesen abfängt.</p>
|
||||
<p><strong>2. Die Antwort enthält Status und Gap, nicht nur Zahlen.</strong> Ein Aufrufer, der nur die Mengen bekommt, kann nicht unterscheiden, ob er ein bewiesenes Optimum oder einen Zeitlimit-Abbruch in der Hand hält. Das <code>Loesung</code>-Objekt geht deshalb vollständig durch die Schnittstelle — dasselbe <a href="anhang-glossar.md#gloss:dto-data-transfer-object" class="glossar-link">DTO</a>, das in <a href="praxisfallen.html#sec:praxisfallen-solverwechsel">Abschnitt 22.7</a> schon eine Prozessgrenze überlebt hat.</p>
|
||||
<p><strong>3. Die Abnahmeprüfung läuft auch hier.</strong> <code>pruefe_loesung()</code> steht im Arbeiter, nicht nur im Skript. Ein Dienst ohne sie liefert Fehler schneller aus, als ein Mensch sie finden könnte.</p>
|
||||
</blockquote>
|
||||
<h3 id="threads-oder-prozesse-eine-messung-keine-meinung">Threads oder Prozesse? Eine Messung, keine Meinung</h3>
|
||||
<p>Punkt 6 des Selbsttests misst, ob vier Solverläufe in Threads sich überlappen. Das Ergebnis ist ein Faktor von rund 3,5 bei vier Threads: <strong>OR-Tools rechnet in C++ und gibt den GIL während <code>Solve()</code> frei.</strong> Ein Threadpool genügt also, es braucht keine eigenen Prozesse.</p>
|
||||
<p>Punkt 6 des Selbsttests misst, ob vier Solverläufe in Threads sich überlappen. Das Ergebnis ist ein Faktor von rund 3,5 bei vier Threads: <strong>OR-Tools rechnet in C++ und gibt den <a href="anhang-glossar.md#gloss:gil-global-interpreter-lock" class="glossar-link">GIL</a> während <code>Solve()</code> frei.</strong> Ein Threadpool genügt also, es braucht keine eigenen Prozesse.</p>
|
||||
<p>Das gilt aber nicht allgemein. Eine in reinem Python geschriebene Heuristik (<a href="metaheuristiken.html#kap-metaheuristiken">Kapitel 9</a>) hält den GIL die ganze Zeit — für sie braucht derselbe Dienst einen <code>ProcessPoolExecutor</code>. Die Entscheidung gehört gemessen, nicht angenommen.</p>
|
||||
<blockquote>
|
||||
<p><strong>⚠️ Der erste Entwurf dieser Messung war falsch</strong></p>
|
||||
|
|
|
|||
Loading…
Reference in a new issue