Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
<!doctype html>
< html lang = "de" >
< head >
< meta charset = "utf-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1" / >
< title > Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten · Optimierte Entscheidungsfindung mit Python< / title >
< script >
(function () {
try {
var t = localStorage.getItem("or-theme");
if (t) document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
< / script >
< link rel = "stylesheet" href = "assets/highlight.css" / >
< link rel = "stylesheet" href = "katex/katex.min.css" / >
< script defer = "" src = "katex/katex.min.js" > < / script >
< script > d o c u m e n t . a d d E v e n t L i s t e n e r ( " D O M C o n t e n t L o a d e d " , f u n c t i o n ( ) {
var mathElements = document.getElementsByClassName("math");
var macros = [];
for (var i = 0; i < mathElements.length ; i + + ) {
var texText = mathElements[i].firstChild;
if (mathElements[i].tagName == "SPAN") {
katex.render(texText.data, mathElements[i], {
displayMode: mathElements[i].classList.contains('display'),
throwOnError: false,
macros: macros,
fleqn: false
});
}}
// Der Browser springt zu einem #anker in der URL schon beim ersten Rendern
// an, BEVOR die KaTeX-Formeln oben im Text ihre finale Hoehe bekommen -
// durch den Reflow landet der Anker danach zu weit unten. Nach dem
// Formel-Rendering hier erneut zum Anker springen, das behebt es.
if (location.hash) {
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
if (ziel) ziel.scrollIntoView({behavior: "instant", block: "start"});
}
});
< / script >
< link rel = "stylesheet" href = "assets/site.css" / >
< / head >
< body >
< svg style = "display:none" aria-hidden = "true" > < symbol id = "icon-menu" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" >
< line x1 = "3" y1 = "6" x2 = "21" y2 = "6" / > < line x1 = "3" y1 = "12" x2 = "21" y2 = "12" / > < line x1 = "3" y1 = "18" x2 = "21" y2 = "18" / >
< / symbol >
< symbol id = "icon-search" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" >
< circle cx = "11" cy = "11" r = "7" / > < line x1 = "21" y1 = "21" x2 = "16.2" y2 = "16.2" / >
< / symbol >
< symbol id = "icon-sun" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" >
< circle cx = "12" cy = "12" r = "4.5" / >
< line x1 = "12" y1 = "1.5" x2 = "12" y2 = "4" / > < line x1 = "12" y1 = "20" x2 = "12" y2 = "22.5" / >
< line x1 = "1.5" y1 = "12" x2 = "4" y2 = "12" / > < line x1 = "20" y1 = "12" x2 = "22.5" y2 = "12" / >
< line x1 = "4.5" y1 = "4.5" x2 = "6.2" y2 = "6.2" / > < line x1 = "17.8" y1 = "17.8" x2 = "19.5" y2 = "19.5" / >
< line x1 = "19.5" y1 = "4.5" x2 = "17.8" y2 = "6.2" / > < line x1 = "6.2" y1 = "17.8" x2 = "4.5" y2 = "19.5" / >
< / symbol >
< symbol id = "icon-moon" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z" / >
< / symbol >
< symbol id = "icon-chevron-left" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< polyline points = "15 4 7 12 15 20" / >
< / symbol >
< symbol id = "icon-chevron-right" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< polyline points = "9 4 17 12 9 20" / >
< / symbol >
< symbol id = "icon-check" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< polyline points = "4 13 9.5 18.5 20 6" / >
< / symbol >
< symbol id = "icon-external-link" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5" / >
< polyline points = "14.5 3.5 20.5 3.5 20.5 9.5" / > < line x1 = "11" y1 = "13" x2 = "20" y2 = "4" / >
< / symbol >
< symbol id = "icon-book" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z" / >
< path d = "M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z" / >
< / symbol >
< symbol id = "icon-copy" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< rect x = "9" y = "9" width = "13" height = "13" rx = "2" / > < path d = "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" / >
< / symbol >
< symbol id = "icon-download" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M12 3v12" / > < polyline points = "7 10 12 15 17 10" / > < path d = "M4 19.5h16" / >
< / symbol > < / svg >
< header class = "site-header" >
< button type = "button" class = "icon-btn" id = "sidebar-toggle" aria-label = "Menü öffnen" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-menu" > < / use > < / svg > < / button >
< a class = "brand" href = "index.html" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-book" > < / use > < / svg > < span > Optimierte Entscheidungsfindung mit Python< / span > < / a >
< div class = "site-search" >
< input id = "suche-eingabe" type = "search" placeholder = "Suchen …" aria-label = "Suche" autocomplete = "off" / >
< svg class = "icon such-icon" aria-hidden = "true" > < use href = "#icon-search" > < / use > < / svg >
< div id = "suche-ergebnisse" class = "suche-ergebnisse" hidden > < / div >
< / div >
< button type = "button" class = "icon-btn" id = "theme-toggle" aria-label = "Darstellung umschalten" >
< svg class = "icon icon-sun" aria-hidden = "true" > < use href = "#icon-sun" > < / use > < / svg > < svg class = "icon icon-moon" aria-hidden = "true" > < use href = "#icon-moon" > < / use > < / svg >
< / button >
< / header >
< div class = "site-body" >
< div class = "sidebar-overlay" id = "sidebar-overlay" hidden > < / div >
2026-09-10 00:07:01 +02:00
< nav class = "sidebar" id = "sidebar" aria-label = "Kapitelnavigation" > < div class = "sidebar-inhalt" > < details class = "sidebar-gruppe" > < summary > Einstieg< / summary > < ul > < li data-kapitel = "vorwort.html" > < a href = "vorwort.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Vorwort & Lesehilfe< / span > < / a > < / li > < li data-kapitel = "notation.html" > < a href = "notation.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Notation & Abkürzungen< / span > < / a > < / li > < / ul > < / details > < details class = "sidebar-gruppe" open > < summary > Teil I: Grundlagen des Operations Research< / summary > < ul > < li data-kapitel = "einfuehrung.html" > < a href = "einfuehrung.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 1: Einführung in Operations Research — Vom Ursprung zur mathematischen Entscheidungsfindung< / span > < / a > < / li > < li data-kapitel = "fundament.html" > < a href = "fundament.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}< / span > < / a > < / li > < li data-kapitel = "oekosystem.html" class = "aktiv" > < a href = "oekosystem.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten< / span > < / a > < / li > < li data-kapitel = "modellierung.html" > < a href = "modellierung.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 4: Vom Management-Wunsch zum Modell< / span > < / a > < / li > < li data-kapitel = "synthese-grundlagen.html" > < a href = "synthese-grundlagen.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Synthese Teil I< / span > < / a > < / li > < / ul > < / details > < details class = "sidebar-gruppe" > < summary > Teil II: Die Kernverfahren der deterministischen Optimierung< / summary > < ul > < li data-kapitel = "lp.html" > < a href = "lp.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 5: Lineare Programmierung — Simplex, Dualität und Schattenpreise< / span > < / a > < / li > < li data-kapitel = "milp.html" > < a href = "milp.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 6: Gemischt-ganzzahlige Optimierung — Diskrete Entscheidungen und Branch-and-Bound{idx:Branch-and-Bound}< / span > < / a > < / li > < li data-kapitel = "cpsat.html" > < a href = "cpsat.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 7: Constraint Programming mit CP-SAT{idx:CP-SAT} — Logik, Scheduling und Zuweisung< / span > < / a > < / li > < li data-kapitel = "graphen.html" > < a href = "graphen.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 8: Graphen, Flüsse und Touren — Min-Cost-Flow, Matching und VRP< / span > < / a > < / li > < li data-kapitel = "metaheuristiken.html" > < a href = "metaheuristiken.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 9: Metaheuristiken — wenn der exakte Solver aussteigt< / span > < / a > < / li > < li data-kapitel = "dekomposition.html" > < a href = "dekomposition.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Kapitel 10: Spaltengenerierung — das Modell umbauen statt die Lösung raten< / span > < / a > < / li > < li data-kapitel = "synthese-kernverfahren.html" > < a href = "synthese-kernverfahren.html" > < span class = "fortschritt-haken" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < / span > < span > Synthese Teil II< / span > < / a > < / li > < / ul > < / details > < details class = "sidebar-gruppe"
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< main class = "content" >
< nav class = "breadcrumb" aria-label = "Breadcrumb" > < a href = "index.html" > Start< / a > › < span > Teil I< / span > › < span > Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten< / span > < / nav >
2026-09-10 00:07:01 +02:00
< nav class = "prev-next" > < a class = "prev-next-knopf prev-next-prev" href = "fundament.html" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-chevron-left" > < / use > < / svg > < span > < small > Zurück< / small > Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}< / span > < / a > < a class = "prev-next-knopf prev-next-next" href = "modellierung.html" > < span > < small > Weiter< / small > Kapitel 4: Vom Management-Wunsch zum Modell< / span > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-chevron-right" > < / use > < / svg > < / a > < / nav >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< article >
< h1 id = "kap-oekosystem" > Kapitel 3: Das Python-Ökosystem für OR — Solver, Bindings und Modellierungsschichten< / h1 >
< div class = "card card-blick" >
< blockquote >
< p > < strong > 📌 Kapitel auf einen Blick< / strong > < / p >
< p > < strong > Worum geht es?< / strong > Warum es für Optimierung in Python mehrere konkurrierende Bibliotheken gibt, was sie unterscheidet, und wie Sie in unter einer Minute die richtige auswählen.< / p >
< p > < strong > Voraussetzungen:< / strong > < a href = "einfuehrung.html#kap-einfuehrung" > Kapitel 1< / a > und < a href = "fundament.html#kap-fundament" > Kapitel 2< / a > .< / p >
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.
2026-09-10 17:40:29 +02:00
< p > < strong > Danach können Sie:< / strong > Für ein gegebenes Problem begründet einen < a href = "anhang-glossar.md#gloss:solver" class = "glossar-link" > Solver< / a > wählen, dasselbe Modell in verschiedenen Bibliotheken formulieren, die Ergebnisse gegeneinander prüfen — und messen, ob Ihre Laufzeit überhaupt im Solver entsteht oder schon davor.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > < strong > Zeitbedarf:< / strong > ca. 4 Stunden.< / p >
< p > < strong > Programme:< / strong > < br / >
< code > Ein_System_Vier_Ansaetze.py< / code > < br / >
< code > Solver_Wahl.py< / code > < br / >
< code > Modellierungsschichten.py< / code > < br / >
< code > Vektorisierte_Modellgenerierung.py< / code > < / p >
Veroeffentlichung: Lizenzen, README, Colab-Entscheidung, JupyterLab im Image
Sechs Dinge, alle fuer das jetzt oeffentliche Repository.
LIZENZEN. LICENSE traegt die MIT-Lizenz fuer den Programmcode (alle .py,
Notebooks, Dockerfile, pyproject.toml), LICENSE-TEXT.md die CC BY-SA 4.0 fuer
Buchtext, PDF, Website und Diagramme. Beide Dateien nennen ausdruecklich, was
sie abdecken und was nicht - ein Codeblock im Buchtext bleibt Programmcode und
steht unter MIT. Vorher hatte das Repository gar keine Lizenz, womit
standardmaessig "alle Rechte vorbehalten" galt und niemand die 76 Programme
haette weiterverwenden duerfen.
COLAB. COLAB_BASIS_URL steht jetzt auf "". Der Platzhalter zeigte auf GitHub,
das Repository liegt auf einer eigenen Forgejo-Instanz - und Colab oeffnet
Notebooks NUR aus GitHub, Google Drive oder einem Upload. Die URL-Form
colab.research.google.com/github/... ist fest auf GitHub verdrahtet; eine
selbstgehostete Adresse dort einzusetzen ergaebe keinen Link zum eigenen
Server, sondern einen toten GitHub-Link. Der Kommentar im Quelltext ging von
GitHub aus und war damit selbst irrefuehrend; er ist ersetzt.
Verloren geht nichts: Die 25 Notebooks liegen neben der Website und bekommen
einen echten Download-Link, jetzt mit dem Hinweis, was man damit tut -
"herunterladen und in Jupyter oeffnen, in Colab hochladen oder mit dem
Kurs-Image starten".
JUPYTERLAB IM KURS-IMAGE. Neue pyproject-Gruppe [notebook] mit jupyterlab,
die Notebooks werden ins Image kopiert, und ein kleiner Startbefehl macht
beide Betriebsarten ohne --entrypoint moeglich: ohne Argument JupyterLab, mit
Argument ein einzelnes Programm. Gebaut und geprueft - Rucksack.py laeuft,
JupyterLab antwortet mit HTTP 200 und zeigt alle 25 Notebooks. Image 1,31 ->
1,46 GB. Es laeuft ohne Token, deshalb im README die Portfreigabe an
127.0.0.1 gebunden.
README KOMPLETT NEU. Es war die Bau-Anleitung eines privaten Verzeichnisses
und ist jetzt die Visitenkarte eines oeffentlichen Repositorys: was das Buch
ist, wo man es liest, drei Wege die Beispiele auszufuehren (Container,
schlanke Installation, alles auf einmal), was hier liegt, wie man baut, die
Colab-Frage, die Lizenzen und wie man mitwirkt. Alle relativen Links
geprueft: 0 tot.
.gitattributes. Ohne die Datei entschied core.autocrlf des jeweiligen
Rechners, was beim Klonen passiert - ein Windows-Leser bekam CRLF-Rauschen in
jedem Diff. Jetzt: im Repository immer LF, im Arbeitsverzeichnis passend zum
System, Binaerdateien ausdruecklich ausgenommen. Der Bestand war bereits
sauber (git add --renormalize aendert null Dateien). Zusaetzlich sind die
erzeugten Verzeichnisse als linguist-generated markiert, sonst zaehlt die
Sprachstatistik das Repository als HTML-Projekt.
PROGRESS.md: Remote-Repository als erledigt markiert, der Colab-Befund
festgehalten. CLAUDE.md um Veroeffentlichung, Lizenzen und die neuen Dateien
ergaenzt.
Geprueft: --check ohne Fehler, 0 tote README-Links, 76 Programme unveraendert,
33 pytest-Tests, pyproject baut mit acht Gruppen, PDF 759 Seiten (eine
weniger - die Colab-Zeile entfaellt in 25 Kapiteln).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:54:04 +02:00
< p > < strong > Notebook:< / strong > < a href = "Notebooks_04/oekosystem.ipynb" > oekosystem.ipynb< / a > — herunterladen und in Jupyter öffnen, in Colab hochladen oder mit dem Kurs-Image starten< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / blockquote >
< / div >
< hr / >
< h2 id = "sec:oekosystem-schnellstart" > 3.1 In 5 Minuten gelöst< / h2 >
< div class = "card card-schnellstart" >
< blockquote >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< p > < strong > 🚀 In 5 Minuten gelöst: Ein < a href = "anhang-glossar.md#gloss:lineare-programmierung-lp" class = "glossar-link" > LP (Lineare Programmierung)< / a > ohne jede Installation< / strong > < / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > Ein Futtermittelhersteller mischt zwei Rohstoffe zu möglichst geringen Kosten. Jeder Kilogramm Mischung muss mindestens 20 g Protein und 5 g Fett enthalten.< / p >
< table >
< thead >
< tr class = "header" >
< th > Rohstoff< / th >
< th style = "text-align: right;" > Preis je kg< / th >
< th style = "text-align: right;" > Protein je kg< / th >
< th style = "text-align: right;" > Fett je kg< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > Weizenschrot< / td >
< td style = "text-align: right;" > 0,42 €< / td >
< td style = "text-align: right;" > 12 g< / td >
< td style = "text-align: right;" > 2 g< / td >
< / tr >
< tr class = "even" >
< td > Sojaschrot< / td >
< td style = "text-align: right;" > 0,88 €< / td >
< td style = "text-align: right;" > 44 g< / td >
< td style = "text-align: right;" > 15 g< / td >
< / tr >
< / tbody >
< / table >
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.
2026-09-10 17:40:29 +02:00
< p > Sie brauchen dafür < strong > keine< / strong > zusätzliche Bibliothek — < a href = "anhang-glossar.md#gloss:scipy" class = "glossar-link" > SciPy< / a > genügt, und SciPy ist in jeder wissenschaftlichen Python-Installation schon da:< / 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" > 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 >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< span id = "cb1-2" > < a href = "#cb1-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb1-3" > < a href = "#cb1-3" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # linprog MINIMIERT und kennt nur " < =" . Mindestgehalte werden also negiert.< / span > < / span >
< span id = "cb1-4" > < a href = "#cb1-4" aria-hidden = "true" tabindex = "-1" > < / a > res < span class = "op" > =< / span > linprog(c< span class = "op" > =< / span > [< span class = "fl" > 0.42< / span > , < span class = "fl" > 0.88< / span > ], < span class = "co" > # Kosten je kg< / span > < / span >
< span id = "cb1-5" > < a href = "#cb1-5" aria-hidden = "true" tabindex = "-1" > < / a > A_ub< span class = "op" > =< / span > [[< span class = "op" > -< / span > < span class = "dv" > 12< / span > , < span class = "op" > -< / span > < span class = "dv" > 44< / span > ], [< span class = "op" > -< / span > < span class = "dv" > 2< / span > , < span class = "op" > -< / span > < span class = "dv" > 15< / span > ]], < span class = "co" > # -Protein, -Fett< / span > < / span >
< span id = "cb1-6" > < a href = "#cb1-6" aria-hidden = "true" tabindex = "-1" > < / a > b_ub< span class = "op" > =< / span > [< span class = "op" > -< / span > < span class = "dv" > 20< / span > , < span class = "op" > -< / span > < span class = "dv" > 5< / span > ], < span class = "co" > # > = 20 g bzw. > = 5 g< / span > < / span >
< span id = "cb1-7" > < a href = "#cb1-7" aria-hidden = "true" tabindex = "-1" > < / a > A_eq< span class = "op" > =< / span > [[< span class = "dv" > 1< / span > , < span class = "dv" > 1< / span > ]], b_eq< span class = "op" > =< / span > [< span class = "dv" > 1< / span > ], < span class = "co" > # ergibt zusammen 1 kg< / span > < / span >
< span id = "cb1-8" > < a href = "#cb1-8" aria-hidden = "true" tabindex = "-1" > < / a > bounds< span class = "op" > =< / span > [(< span class = "dv" > 0< / span > , < span class = "dv" > 1< / span > ), (< span class = "dv" > 0< / span > , < span class = "dv" > 1< / span > )])< / span >
< span id = "cb1-9" > < a href = "#cb1-9" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb1-10" > < a href = "#cb1-10" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (res.message)< / span >
< span id = "cb1-11" > < a href = "#cb1-11" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Weizen < / span > < span class = "sc" > {< / span > res< span class = "sc" > .< / span > x[< span class = "dv" > 0< / span > ]< span class = "sc" > :.3f}< / span > < span class = "ss" > kg, Soja < / span > < span class = "sc" > {< / span > res< span class = "sc" > .< / span > x[< span class = "dv" > 1< / span > ]< span class = "sc" > :.3f}< / span > < span class = "ss" > kg -> < / span > < span class = "sc" > {< / span > res< span class = "sc" > .< / span > fun< span class = "sc" > :.4f}< / span > < span class = "ss" > EUR/kg" < / span > )< / span > < / code > < / pre > < / div >
< p > < strong > Ausgabe:< / strong > < / p >
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.
2026-09-10 17:40:29 +02:00
< pre > < code > Optimization terminated successfully. ([HiGHS](anhang-glossar.md#gloss:highs){.glossar-link} Status 7: Optimal)
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
Weizen 0.750 kg, Soja 0.250 kg -> 0.5350 EUR/kg< / code > < / pre >
< / blockquote >
< / div >
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.
2026-09-10 17:40:29 +02:00
< p > < strong > Sie haben soeben HiGHS benutzt< / strong > — denselben C++-Solver, der auch hinter < code > highspy< / code > , hinter < a href = "anhang-glossar.md#gloss:cvxpy" class = "glossar-link" > CVXPY< / a > und in vielen kommerziellen Systemen steckt. < code > scipy.optimize.linprog< / code > ist nur die dünnste denkbare Hülle darum.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > Das ist die zentrale Botschaft dieses Kapitels: < strong > Modellierungsschicht und Solver sind zwei verschiedene Dinge.< / strong > Die Bibliothek, in der Sie Ihr Modell hinschreiben, bestimmt, wie angenehm die Arbeit ist. Der Solver dahinter bestimmt, wie schnell gerechnet wird. Beide lassen sich unabhängig voneinander tauschen — und genau davon handelt der Rest des Kapitels.< / p >
< blockquote >
< p > < strong > ⚠️ Zwei Stolpersteine stecken schon in diesen sechs Zeilen< / strong > < / p >
< ul >
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.
2026-09-10 17:40:29 +02:00
< li > < code > linprog< / code > < strong > minimiert immer< / strong > . Wer maximieren will, negiert die < a href = "anhang-glossar.md#gloss:zielfunktion" class = "glossar-link" > Zielfunktion< / a > — und darf nicht vergessen, das Ergebnis zurückzudrehen.< / li >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< li > < code > linprog< / code > kennt < strong > nur < code > < =< / code > < / strong > . Ein Mindestgehalt „< span class = "math inline" > \ge 20< / span > “ wird zu „< span class = "math inline" > -12x_1 - 44x_2 \le -20< / span > “. Ein Vorzeichenfehler an dieser Stelle liefert eine perfekt aussehende Lösung, die das Gegenteil des Gewollten erfüllt.< / li >
< / ul >
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.
2026-09-10 17:40:29 +02:00
< p > Beide Fallen verschwinden, sobald man eine Modellierungsschicht wie CVXPY oder < a href = "anhang-glossar.md#gloss:pyomo" class = "glossar-link" > Pyomo< / a > benutzt, in der < code > > =< / code > einfach < code > > =< / code > heißt. Das ist ein Hauptgrund, warum es sie gibt.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / blockquote >
< hr / >
< h2 id = "sec:oekosystem-lernziele" > 3.2 Lernziele< / h2 >
< p > Nach diesem Kapitel können Sie …< / p >
< ol type = "1" >
< li > … die Zwei-Schichten-Architektur (Modellierung vs. Solver) erklären.< / li >
< li > … anhand von drei Fragen entscheiden, welche Bibliothek zu Ihrem Problem passt.< / li >
< li > … dasselbe lineare Programm in < code > scipy.optimize< / code > , < code > highspy< / code > , < code > ortools< / code > und < code > cvxpy< / code > formulieren.< / li >
< li > … Ergebnisse verschiedener Solver gegeneinander validieren (< em > Cross-Check< / em > ).< / li >
< li > … einschätzen, wann sich der Aufwand einer Low-Level-Schnittstelle lohnt und wann nicht.< / li >
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.
2026-09-10 17:40:29 +02:00
< li > … Pyomo und < a href = "anhang-glossar.md#gloss:linopy" class = "glossar-link" > Linopy< / a > einordnen und begründen, wann sich der Umstieg auf sie lohnt.< / li >
< li > … Aufbau- und Lösezeit getrennt messen und einen Modellaufbau mit < a href = "anhang-glossar.md#gloss:numpy" class = "glossar-link" > NumPy< / a > oder < a href = "anhang-glossar.md#gloss:polars" class = "glossar-link" > Polars< / a > vektorisieren, statt ihn in Python-Schleifen zu erzeugen.< / li >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / ol >
< hr / >
< h2 id = "sec:oekosystem-die-zwei-schichten-architektur" > 3.3 Die Zwei-Schichten-Architektur< / h2 >
< p > Im modernen Operations Research schreibt man Optimierungsalgorithmen nicht selbst. Man nutzt eine < strong > zweischichtige Architektur< / strong > :< / p >
< ol type = "1" >
< li > < strong > Modellierungsschicht (< em > Frontend< / em > , DSL):< / strong > Python-Bibliotheken, mit denen Entscheidungsvariablen, Zielfunktion und Nebenbedingungen in mathematiknaher Syntax formuliert werden. Hier arbeiten Sie.< / li >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< li > < strong > Solver-Schicht (< em > Backend< / em > , Engine):< / strong > Hochoptimierte C++- oder Fortran-Bibliotheken, die das Modell in Matrixstrukturen übersetzen und mit spezialisierten Algorithmen lösen (< a href = "anhang-glossar.md#gloss:dual-simplex" class = "glossar-link" > Dual Simplex< / a > , Interior-Point, < a href = "anhang-glossar.md#gloss:branch-and-cut" class = "glossar-link" > Branch-and-Cut< / a > , < a href = "anhang-glossar.md#gloss:cdcl" class = "glossar-link" > CDCL< / a > -< a href = "anhang-glossar.md#gloss:sat-boolean-satisfiability" class = "glossar-link" > SAT (Boolean Satisfiability)< / a > -Suche).< / li >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / ol >
< figure >
< img src = "bilder_04/kap03_solver_architektur.svg" alt = "Abb. 3.1: Modellierungsschicht und Solver-Engines" / >
< figcaption aria-hidden = "true" > Abb. 3.1: Modellierungsschicht und Solver-Engines< / figcaption >
< / figure >
< blockquote >
< p > < strong > 🎯 Merksatz< / strong > Die Modellierungsschicht bestimmt, wie angenehm Sie arbeiten. Die Solver-Schicht bestimmt, wie schnell gerechnet wird. Beides ist entkoppelt — man kann dieselbe CVXPY-Formulierung mit fünf verschiedenen Solvern lösen.< / p >
< / blockquote >
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.
2026-09-10 17:40:29 +02:00
< p > < strong > Warum diese Trennung nützlich ist:< / strong > Ein CVXPY-Modell, das heute mit dem freien Solver Clarabel läuft, läuft morgen ohne Codeänderung mit dem kommerziellen < a href = "anhang-glossar.md#gloss:gurobi" class = "glossar-link" > Gurobi< / a > — man tauscht ein Argument. Das schützt vor Herstellerbindung und erlaubt, im Projekt klein anzufangen.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< hr / >
< h2 id = "sec:oekosystem-die-werkzeuge-im-vergleich" > 3.4 Die Werkzeuge im Vergleich< / h2 >
< table >
< colgroup >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Bibliothek< / th >
< th > Stärken & Problemklassen< / th >
< th > Wann einsetzen?< / th >
< th > Typische Backends< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > scipy.optimize< / code > < / td >
< td > Standard-LPs, kontinuierliche nichtlineare Optimierung, Wurzelsuche< / td >
< td > Für elementare LPs und schnelle Prototypen ohne zusätzliche schwere Abhängigkeiten< / td >
< td > HiGHS (< code > highs-ds< / code > , < code > highs-ipm< / code > )< / td >
< / tr >
< tr class = "even" >
< td > < code > ortools< / code > (Google)< / td >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< td > Diskrete Planung, Schichten, Vertretungspläne, Routing (< a href = "anhang-glossar.md#gloss:vehicle-routing-problem-vrp" class = "glossar-link" > VRP (Vehicle Routing Problem)< / a > /< a href = "anhang-glossar.md#gloss:tsp-traveling-salesperson-problem" class = "glossar-link" > TSP (Traveling Salesperson Problem)< / a > ), kombinatorische Logik< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< td > Wenn diskrete Ja/Nein-Entscheidungen, Zeitfenster und Wenn-Dann-Regeln dominieren< / td >
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.
2026-09-10 17:40:29 +02:00
< td > < a href = "anhang-glossar.md#gloss:cp-sat" class = "glossar-link" > CP-SAT< / a > , < a href = "anhang-glossar.md#gloss:glop" class = "glossar-link" > GLOP< / a > , Routing Engine< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / tr >
< tr class = "odd" >
< td > < code > cvxpy< / code > < / td >
< td > Konvexe Optimierung, quadratische Programme, Risikomodelle, Portfolios< / td >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< td > < strong > Erste Wahl für Finanzoptimierung< / strong > , Markowitz, < a href = "anhang-glossar.md#gloss:conditional-value-at-risk-cvar" class = "glossar-link" > CVaR (Conditional Value at Risk)< / a > , < span class = "math inline" > L_1< / span > -< a href = "anhang-glossar.md#gloss:transaktionskosten" class = "glossar-link" > Transaktionskosten< / a > < / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< td > Clarabel, OSQP, ECOS, SCS, HiGHS< / td >
< / tr >
< tr class = "even" >
< td > < code > highspy< / code > < / td >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< td > Direkte Solver-Steuerung für LP/< a href = "anhang-glossar.md#gloss:gemischt-ganzzahlige-optimierung-milp" class = "glossar-link" > MILP (Gemischt-ganzzahlige Optimierung)< / a > , maximale Kontrolle< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< td > Wenn Sie Modelle wiederholt und schnell aufbauen oder Solver-Details steuern müssen< / td >
< td > HiGHS< / td >
< / tr >
< tr class = "odd" >
< td > < code > Pyomo< / code > < / td >
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.
2026-09-10 17:40:29 +02:00
< td > Universelle Modellierungssprache (LP, MILP, < a href = "anhang-glossar.md#gloss:minlp" class = "glossar-link" > MINLP< / a > )< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< td > Industrielle Großmodelle mit strikter Trennung von Daten und Modell< / td >
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.
2026-09-10 17:40:29 +02:00
< td > HiGHS, Gurobi, CPLEX, < a href = "anhang-glossar.md#gloss:scip" class = "glossar-link" > SCIP< / a > , IPOPT< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / tr >
< tr class = "even" >
< td > < code > Linopy< / code > < / td >
< td > LP/MILP über beschriftete Arrays (< code > xarray< / code > )< / td >
< td > Modelle mit zehntausenden gleichartigen Nebenbedingungen (Energie, Netze, Zeitreihen)< / td >
< td > HiGHS, GLPK, CBC, Gurobi< / td >
< / tr >
< / tbody >
< / table >
< h3 id = "die-entscheidung-in-drei-fragen" > Die Entscheidung in drei Fragen< / h3 >
< p > Statt die Tabelle auswendig zu lernen, beantworten Sie drei Fragen:< / p >
< blockquote >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< p > < strong > Frage 1: Gibt es Ja/Nein-Entscheidungen oder Reihenfolgen?< / strong > → < strong > Ja:< / strong > < a href = "anhang-glossar.md#gloss:or-tools" class = "glossar-link" > OR-Tools< / a > (< a href = "anhang-glossar.md#gloss:constraint-programming-cp" class = "glossar-link" > CP (Constraint Programming)< / a > -SAT) bei Zuweisung/< a href = "anhang-glossar.md#gloss:scheduling" class = "glossar-link" > Scheduling< / a > , oder MILP über HiGHS bei ökonomischen Fixkostenmodellen. → < strong > Nein:< / strong > weiter zu Frage 2.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > < strong > Frage 2: Ist die Zielfunktion linear?< / strong > → < strong > Ja:< / strong > < code > scipy.optimize.linprog< / code > (klein) oder < code > highspy< / code > (groß, wiederholt). → < strong > Nein, aber konvex< / strong > (Quadrate, Normen, < code > log< / code > , < code > exp< / code > in konvexer Kombination): < strong > CVXPY< / strong > . → < strong > Nein und nicht konvex:< / strong > < code > scipy.optimize.minimize< / code > — im Bewusstsein, dass nur ein lokales Optimum herauskommt.< / p >
< p > < strong > Frage 3: Ist es ein Routing-Problem mit Fahrzeugen, Depots und Zeitfenstern?< / strong > → < strong > Ja:< / strong > OR-Tools < strong > Routing Library< / strong > (nicht CP-SAT von Hand nachbauen).< / p >
< / blockquote >
< p > Das folgende kleine Programm gießt diese Logik in Code — als Nachschlagehilfe für den eigenen Gebrauch.< / p >
< div class = "sourceCode" id = "cb3" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb3-1" > < a href = "#cb3-1" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > #!/usr/bin/env python3< / span > < / span >
< span id = "cb3-2" > < a href = "#cb3-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-3" > < a href = "#cb3-3" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Solver_Wahl.py< / span > < / span >
< span id = "cb3-4" > < a href = "#cb3-4" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb3-5" > < a href = "#cb3-5" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Kapitel Oekosystem: Entscheidungshilfe zur Solverwahl.< / span > < / span >
< span id = "cb3-6" > < a href = "#cb3-6" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Beantwortet drei Fragen und empfiehlt Bibliothek + Backend.< / span > < / span >
< span id = "cb3-7" > < a href = "#cb3-7" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb3-8" > < a href = "#cb3-8" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-9" > < a href = "#cb3-9" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > dataclasses < span class = "im" > import< / span > dataclass< / span >
< span id = "cb3-10" > < a href = "#cb3-10" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-11" > < a href = "#cb3-11" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-12" > < a href = "#cb3-12" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "at" > @dataclass< / span > < / span >
< span id = "cb3-13" > < a href = "#cb3-13" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > class< / span > Problem:< / span >
< span id = "cb3-14" > < a href = "#cb3-14" aria-hidden = "true" tabindex = "-1" > < / a > diskrete_entscheidungen: < span class = "bu" > bool< / span > < span class = "co" > # Ja/Nein-Variablen, Zuordnungen, Reihenfolgen?< / span > < / span >
< span id = "cb3-15" > < a href = "#cb3-15" aria-hidden = "true" tabindex = "-1" > < / a > zielfunktion: < span class = "bu" > str< / span > < span class = "co" > # " linear" | " quadratisch" | " konvex" | " beliebig" < / span > < / span >
< span id = "cb3-16" > < a href = "#cb3-16" aria-hidden = "true" tabindex = "-1" > < / a > routing: < span class = "bu" > bool< / span > < span class = "op" > =< / span > < span class = "va" > False< / span > < span class = "co" > # Fahrzeuge, Depots, Zeitfenster?< / span > < / span >
< span id = "cb3-17" > < a href = "#cb3-17" aria-hidden = "true" tabindex = "-1" > < / a > groesse: < span class = "bu" > str< / span > < span class = "op" > =< / span > < span class = "st" > " klein" < / span > < span class = "co" > # " klein" (< 10^3 Var.) | " gross" < / span > < / span >
< span id = "cb3-18" > < a href = "#cb3-18" aria-hidden = "true" tabindex = "-1" > < / a > wiederholte_laeufe: < span class = "bu" > bool< / span > < span class = "op" > =< / span > < span class = "va" > False< / span > < / span >
< span id = "cb3-19" > < a href = "#cb3-19" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-20" > < a href = "#cb3-20" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-21" > < a href = "#cb3-21" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > empfehle(p: Problem) < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > str< / span > , < span class = "bu" > str< / span > , < span class = "bu" > str< / span > ]:< / span >
< span id = "cb3-22" > < a href = "#cb3-22" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Gibt (Bibliothek, Backend, Begründung) zurück." " " < / span > < / span >
< span id = "cb3-23" > < a href = "#cb3-23" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.routing:< / span >
< span id = "cb3-24" > < a href = "#cb3-24" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " ortools.constraint_solver (Routing)" < / span > , < span class = "st" > " Routing Engine" < / span > ,< / span >
< span id = "cb3-25" > < a href = "#cb3-25" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Spezialisierte Heuristiken für VRP/TSP schlagen jedes selbstgebaute Modell." < / span > )< / span >
< span id = "cb3-26" > < a href = "#cb3-26" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-27" > < a href = "#cb3-27" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.diskrete_entscheidungen:< / span >
< span id = "cb3-28" > < a href = "#cb3-28" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.zielfunktion < span class = "kw" > in< / span > (< span class = "st" > " linear" < / span > ,) < span class = "kw" > and< / span > < span class = "kw" > not< / span > p.routing:< / span >
< span id = "cb3-29" > < a href = "#cb3-29" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.groesse < span class = "op" > ==< / span > < span class = "st" > " gross" < / span > :< / span >
< span id = "cb3-30" > < a href = "#cb3-30" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " highspy" < / span > , < span class = "st" > " HiGHS Branch-and-Cut" < / span > ,< / span >
< span id = "cb3-31" > < a href = "#cb3-31" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " MILP mit ökonomischer Struktur (Fixkosten, Kardinalität)." < / span > )< / span >
< span id = "cb3-32" > < a href = "#cb3-32" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " ortools.sat (CP-SAT)" < / span > , < span class = "st" > " CP-SAT" < / span > ,< / span >
< span id = "cb3-33" > < a href = "#cb3-33" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Logische Regeln und Zuweisungen: CP-SAT propagiert sehr effizient." < / span > )< / span >
< span id = "cb3-34" > < a href = "#cb3-34" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " ortools.sat (CP-SAT)" < / span > , < span class = "st" > " CP-SAT" < / span > ,< / span >
< span id = "cb3-35" > < a href = "#cb3-35" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Diskrete Struktur dominiert; CP-SAT verarbeitet auch nichtlineare Logik." < / span > )< / span >
< span id = "cb3-36" > < a href = "#cb3-36" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-37" > < a href = "#cb3-37" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.zielfunktion < span class = "op" > ==< / span > < span class = "st" > " linear" < / span > :< / span >
< span id = "cb3-38" > < a href = "#cb3-38" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.wiederholte_laeufe < span class = "kw" > or< / span > p.groesse < span class = "op" > ==< / span > < span class = "st" > " gross" < / span > :< / span >
< span id = "cb3-39" > < a href = "#cb3-39" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " highspy" < / span > , < span class = "st" > " HiGHS Dual Simplex" < / span > ,< / span >
< span id = "cb3-40" > < a href = "#cb3-40" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Modell einmal aufbauen, Parameter ändern, wiederholt lösen." < / span > )< / span >
< span id = "cb3-41" > < a href = "#cb3-41" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " scipy.optimize.linprog" < / span > , < span class = "st" > " HiGHS" < / span > ,< / span >
< span id = "cb3-42" > < a href = "#cb3-42" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Kleinstes Setup, keine zusätzliche Abhängigkeit." < / span > )< / span >
< span id = "cb3-43" > < a href = "#cb3-43" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-44" > < a href = "#cb3-44" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > p.zielfunktion < span class = "kw" > in< / span > (< span class = "st" > " quadratisch" < / span > , < span class = "st" > " konvex" < / span > ):< / span >
< span id = "cb3-45" > < a href = "#cb3-45" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " cvxpy" < / span > , < span class = "st" > " Clarabel / OSQP" < / span > ,< / span >
< span id = "cb3-46" > < a href = "#cb3-46" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Konvexität wird automatisch geprüft; Portfolio-Standard." < / span > )< / span >
< span id = "cb3-47" > < a href = "#cb3-47" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-48" > < a href = "#cb3-48" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "st" > " scipy.optimize.minimize" < / span > , < span class = "st" > " SLSQP / trust-constr" < / span > ,< / span >
< span id = "cb3-49" > < a href = "#cb3-49" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Nicht konvex: nur lokales Optimum, Startpunkt variieren und vergleichen!" < / span > )< / span >
< span id = "cb3-50" > < a href = "#cb3-50" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-51" > < a href = "#cb3-51" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-52" > < a href = "#cb3-52" aria-hidden = "true" tabindex = "-1" > < / a > BEISPIELE < span class = "op" > =< / span > {< / span >
< span id = "cb3-53" > < a href = "#cb3-53" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Vertretungsplan Schule" < / span > : Problem(< span class = "va" > True< / span > , < span class = "st" > " linear" < / span > , groesse< span class = "op" > =< / span > < span class = "st" > " klein" < / span > ),< / span >
< span id = "cb3-54" > < a href = "#cb3-54" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Produktionsplanung (LP)" < / span > : Problem(< span class = "va" > False< / span > , < span class = "st" > " linear" < / span > , groesse< span class = "op" > =< / span > < span class = "st" > " klein" < / span > ),< / span >
< span id = "cb3-55" > < a href = "#cb3-55" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Produktionsplanung, 50k Var." < / span > : Problem(< span class = "va" > False< / span > , < span class = "st" > " linear" < / span > , groesse< span class = "op" > =< / span > < span class = "st" > " gross" < / span > ),< / span >
< span id = "cb3-56" > < a href = "#cb3-56" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Portfolio Markowitz" < / span > : Problem(< span class = "va" > False< / span > , < span class = "st" > " quadratisch" < / span > ),< / span >
< span id = "cb3-57" > < a href = "#cb3-57" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Portfolio mit max. 5 Titeln" < / span > : Problem(< span class = "va" > True< / span > , < span class = "st" > " quadratisch" < / span > ),< / span >
< span id = "cb3-58" > < a href = "#cb3-58" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Liefertouren mit Zeitfenstern" < / span > : Problem(< span class = "va" > True< / span > , < span class = "st" > " linear" < / span > , routing< span class = "op" > =< / span > < span class = "va" > True< / span > ),< / span >
< span id = "cb3-59" > < a href = "#cb3-59" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Entropie-Allokation (NLP)" < / span > : Problem(< span class = "va" > False< / span > , < span class = "st" > " beliebig" < / span > ),< / span >
< span id = "cb3-60" > < a href = "#cb3-60" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " Backtest, 60x neu optimieren" < / span > : Problem(< span class = "va" > False< / span > , < span class = "st" > " konvex" < / span > , wiederholte_laeufe< span class = "op" > =< / span > < span class = "va" > True< / span > ),< / span >
< span id = "cb3-61" > < a href = "#cb3-61" aria-hidden = "true" tabindex = "-1" > < / a > }< / span >
< span id = "cb3-62" > < a href = "#cb3-62" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb3-63" > < a href = "#cb3-63" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > < span class = "va" > __name__< / span > < span class = "op" > ==< / span > < span class = "st" > " __main__" < / span > :< / span >
< span id = "cb3-64" > < a href = "#cb3-64" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 92< / span > )< / span >
< span id = "cb3-65" > < a href = "#cb3-65" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " SOLVER-EMPFEHLUNG" < / span > )< / span >
< span id = "cb3-66" > < a href = "#cb3-66" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 92< / span > )< / span >
< span id = "cb3-67" > < a href = "#cb3-67" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > name, p < span class = "kw" > in< / span > BEISPIELE.items():< / span >
< span id = "cb3-68" > < a href = "#cb3-68" aria-hidden = "true" tabindex = "-1" > < / a > lib, backend, grund < span class = "op" > =< / span > empfehle(p)< / span >
< span id = "cb3-69" > < a href = "#cb3-69" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "ch" > \n< / span > < span class = "sc" > {< / span > name< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb3-70" > < a href = "#cb3-70" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" -> Bibliothek: < / span > < span class = "sc" > {< / span > lib< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb3-71" > < a href = "#cb3-71" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Backend: < / span > < span class = "sc" > {< / span > backend< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb3-72" > < a href = "#cb3-72" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Grund: < / span > < span class = "sc" > {< / span > grund< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb3-73" > < a href = "#cb3-73" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " < / span > < span class = "ch" > \n< / span > < span class = "st" > " < / span > < span class = "op" > +< / span > < span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 92< / span > )< / span > < / code > < / pre > < / div >
< hr / >
< h2 id = "sec:oekosystem-ein-system-vier-programmieransaetze" > 3.5 Ein System — vier Programmieransätze< / h2 >
< p > Nichts macht die Unterschiede so deutlich wie dasselbe Problem, viermal gelöst. Wir nehmen:< / p >
< p > < span class = "math display" >
\max\ 10x_1 + 15x_2 + 25x_3
< / span > < / p >
< p > < span class = "math display" >
\text{u. d. N.}\quad x_1 + x_2 + 2x_3 \le 40,\qquad 2x_1 + 3x_2 + x_3 \le 50,
\qquad x_1, x_2, x_3 \ge 0
< / span > < / p >
< blockquote >
< p > < strong > ✏️ Handrechnung 3.1: Erwarten Sie das Ergebnis< / strong > < / p >
< p > Bevor Sie das Programm laufen lassen: Welches Produkt ist am attraktivsten?< / p >
< p > Produkt 3 bringt den höchsten Ertrag (25) und verbraucht wenig von Ressource 2 (nur 1). Es verbraucht aber doppelt so viel von Ressource 1. Rechnen wir den Ertrag < strong > pro Einheit Engpassressource< / strong > :< / p >
< table >
< thead >
< tr class = "header" >
< th > Produkt< / th >
< th > Ertrag< / th >
< th > pro Einheit Ressource 1< / th >
< th > pro Einheit Ressource 2< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < span class = "math inline" > x_1< / span > < / td >
< td > 10< / td >
< td > < span class = "math inline" > 10/1 = 10< / span > < / td >
< td > < span class = "math inline" > 10/2 = 5< / span > < / td >
< / tr >
< tr class = "even" >
< td > < span class = "math inline" > x_2< / span > < / td >
< td > 15< / td >
< td > < span class = "math inline" > 15/1 = 15< / span > < / td >
< td > < span class = "math inline" > 15/3 = 5< / span > < / td >
< / tr >
< tr class = "odd" >
< td > < span class = "math inline" > x_3< / span > < / td >
< td > 25< / td >
< td > < span class = "math inline" > 25/2 = 12{,}5< / span > < / td >
< td > < span class = "math inline" > 25/1 = 25< / span > < / td >
< / tr >
< / tbody >
< / table >
< p > Kein Produkt ist in beiden Spalten der Sieger — deshalb braucht es überhaupt einen Optimierer. Probieren wir zwei Randlösungen: * Nur < span class = "math inline" > x_3< / span > : Ressource 1 erlaubt < span class = "math inline" > 40/2 = 20< / span > Stück, Ressource 2 erlaubt 50. Also < span class = "math inline" > x_3 = 20< / span > , Ertrag < span class = "math inline" > = 500< / span > . Ressource 2 ist dann nur zu < span class = "math inline" > 20/50< / span > ausgelastet. * < span class = "math inline" > x_3 = 20< / span > plus < span class = "math inline" > x_2< / span > ? Ressource 1 ist bereits voll (< span class = "math inline" > 40< / span > ), es geht nichts mehr. * Mischung < span class = "math inline" > x_2< / span > und < span class = "math inline" > x_3< / span > : < span class = "math inline" > x_2 + 2x_3 = 40< / span > und < span class = "math inline" > 3x_2 + x_3 = 50< / span > ⟹ < span class = "math inline" > x_2 = 12< / span > , < span class = "math inline" > x_3 = 14< / span > , Ertrag < span class = "math inline" > = 15\cdot12 + 25\cdot14 = 180 + 350 = 530< / span > . Besser!< / p >
< p > Erwartetes Ergebnis: < strong > < span class = "math inline" > Z^* = 530< / span > < / strong > mit < span class = "math inline" > x_1 = 0< / span > , < span class = "math inline" > x_2 = 12< / span > , < span class = "math inline" > x_3 = 14< / span > .< / p >
< / blockquote >
< blockquote >
< p > < strong > ⚠️ Vorab: eine Stolperfalle der Installation< / strong > < / p >
< p > Auf vielen Systemen lassen sich < strong > < code > ortools< / code > und < code > highspy< / code > nicht im selben Python-Prozess importieren< / strong > . Beide Pakete bringen ihre eigene, unterschiedlich kompilierte Kopie des HiGHS-Solvers mit; der dynamische Linker löst die Symbole dann falsch auf. Die Fehlermeldung sieht so aus:< / p >
< pre > < code > ImportError: .../highspy/_core...so: undefined symbol: _ZN5Highs13releaseMemoryEv< / code > < / pre >
< p > oder umgekehrt< / p >
< pre > < code > ImportError: .../ortools/.libs/libortools.so.9: undefined symbol: _Z19setLocalOptionValue...< / code > < / pre >
< p > Das ist < strong > kein Fehler in Ihrem Code< / strong > , und bei < code > ortools< / code > und < code > highspy< / code > direkt hilft auch die Reihenfolge der Importe nicht — es trifft beide Richtungen. < code > cvxpy< / code > verträgt sich mit beiden, importiert aber ein installiertes < code > highspy< / code > bei der Solver-Erkennung selbst mit (siehe < code > *< / code > ).< / p >
< table >
< thead >
< tr class = "header" >
< th > Kombination in einem Prozess< / th >
< th > Funktioniert?< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > ortools< / code > + < code > highspy< / code > < / td >
< td > ❌ nein (beide Richtungen)< / td >
< / tr >
< tr class = "even" >
< td > < code > ortools< / code > + < code > cvxpy< / code > < / td >
< td > ✅ ja*< / td >
< / tr >
< tr class = "odd" >
< td > < code > highspy< / code > + < code > cvxpy< / code > < / td >
< td > ✅ ja< / td >
< / tr >
< tr class = "even" >
< td > < code > scipy< / code > + beliebig< / td >
< td > ✅ ja< / td >
< / tr >
< / tbody >
< / table >
< p > * ist < code > highspy< / code > installiert, gilt die < code > ortools< / code > +< code > highspy< / code > -Zeile entsprechend: erst < code > cvxpy< / code > , dann < code > ortools< / code > importiert → Crash; erst < code > ortools< / code > , dann < code > cvxpy< / code > → läuft, CVXPY nur ohne HIGHS-Interface.< / p >
< p > < strong > Abhilfe:< / strong > Jeden Solver in einem < strong > eigenen Prozess< / strong > ausführen — genau das tut das folgende Programm. Alternativ: getrennte virtuelle Umgebungen, oder auf < code > highspy< / code > verzichten und HiGHS über < code > scipy.optimize.linprog< / code > bzw. CVXPY ansprechen (dort ist es ohnehin als Backend verfügbar).< / p >
< p > Der Installationstest im Vorspann umgeht die Falle bereits: Er lädt < code > ortools< / code > zuerst, prüft < code > highspy< / code > und < code > cvxpy< / code > in der Paketübersicht nur auf Anwesenheit (< code > importlib.util.find_spec< / code > ) und importiert CVXPY erst im Funktionstest.< / p >
< / blockquote >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< h3 id = "wie-die-isolation-aussieht-wenn-sie-tragen-soll" > Wie die Isolation aussieht, wenn sie tragen soll< / h3 >
< p > „Eigener Prozess” ist schnell gesagt. Die naheliegende Umsetzung — ein Codeschnipsel als Zeichenkette an < code > python -c< / code > übergeben — funktioniert und ist trotzdem die schlechteste: Der Schnipsel ist für Editor, Linter und Testwerkzeug unsichtbar, ein Tippfehler darin fällt erst zur Laufzeit auf, und übergeben lassen sich nur Zeichenketten.< / p >
< p > Tragfähig ist stattdessen: < strong > jeder Solver eine gewöhnliche Funktion mit lokalem Import< / strong > , ausgeführt von einem < code > ProcessPoolExecutor< / code > mit zwei Einstellungen, die zusammen die Garantie ergeben:< / p >
< table >
< colgroup >
< col style = "width: 50%" / >
< col style = "width: 50%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Einstellung< / th >
< th > Wozu< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > mp_context=multiprocessing.get_context("spawn")< / code > < / td >
< td > Der Kindprozess startet mit einem < strong > frischen< / strong > Interpreter, statt den Speicher des Elternprozesses zu erben. Unter Linux ist < code > fork< / code > der Standard — und damit wäre alles, was hier schon importiert ist, auch dort importiert.< / td >
< / tr >
< tr class = "even" >
< td > < code > max_tasks_per_child=1< / code > < / td >
< td > Jede Aufgabe bekommt einen < strong > neuen< / strong > Prozess. Ohne das verwendet der Pool seinen Arbeiter wieder, und beim zweiten Solver ist der Konflikt zurück. Genau dieser Fehler ist leicht zu machen und schwer zu finden.< / td >
< / tr >
< / tbody >
< / table >
< blockquote >
< p > < strong > ⚠️ < code > max_tasks_per_child=1< / code > ist nicht optional< / strong > Ein Pool ohne diese Angabe ist der < strong > Normalfall< / strong > — er soll seine Arbeiter ja wiederverwenden. Wer die Isolation über einen Pool herstellt und das vergisst, hat einen Prozesswechsel programmiert, aber keine Isolation gewonnen: Die zweite Aufgabe landet im selben Interpreter wie die erste. Der Absturz kommt dann nicht beim ersten Solver, sondern beim zweiten — und sieht aus wie ein Problem des zweiten.< / p >
< / blockquote >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< p > Denselben Aufbau verwenden < code > Solverwechsel_CPSAT_HiGHS.py< / code > (< a href = "praxisfallen.html#kap-praxisfallen" > Kapitel 22< / a > ) und < code > Benchmark_Skalierung.py< / code > (< a href = "testing.html#kap-testing" > Kapitel 23< / a > ). Dort wandern zusätzlich < strong > Datenobjekte< / strong > über die Prozessgrenze statt Zeichenketten — möglich, weil Domänenmodell und Lösungs-< a href = "anhang-glossar.md#gloss:dto-data-transfer-object" class = "glossar-link" > DTO (Data Transfer Object)< / a > keinen Solver kennen (< a href = "praxisfallen.html#sec:praxisfallen-or-kern" > Abschnitt 22.6< / a > ).< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< div class = "sourceCode" id = "cb6" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb6-1" > < a href = "#cb6-1" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > #!/usr/bin/env python3< / span > < / span >
< span id = "cb6-2" > < a href = "#cb6-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-3" > < a href = "#cb6-3" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Ein_System_Vier_Ansaetze.py< / span > < / span >
< span id = "cb6-4" > < a href = "#cb6-4" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb6-5" > < a href = "#cb6-5" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Kapitel Oekosystem: Dasselbe LP in vier Bibliotheken.< / span > < / span >
< span id = "cb6-6" > < a href = "#cb6-6" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > max 10*x1 + 15*x2 + 25*x3< / span > < / span >
< span id = "cb6-7" > < a href = "#cb6-7" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > u.d.N. x1 + x2 + 2*x3 < = 40< / span > < / span >
< span id = "cb6-8" > < a href = "#cb6-8" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > 2*x1 + 3*x2 + x3 < = 50< / span > < / span >
< span id = "cb6-9" > < a href = "#cb6-9" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > x > = 0< / span > < / span >
< span id = "cb6-10" > < a href = "#cb6-10" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< span id = "cb6-11" > < a href = "#cb6-11" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Deckt scipy.optimize, highspy, CVXPY und OR-Tools/GLOP ab, mit Kreuzvergleich< / span > < / span >
< span id = "cb6-12" > < a href = "#cb6-12" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > am Ende.< / span > < / span >
< span id = "cb6-13" > < a href = "#cb6-13" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-14" > < a href = "#cb6-14" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > WICHTIG: Jeder Solver laeuft in einem EIGENEN Prozess, weil sich ortools und< / span > < / span >
< span id = "cb6-15" > < a href = "#cb6-15" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > highspy auf vielen Systemen nicht gemeinsam importieren lassen (beide bringen< / span > < / span >
< span id = "cb6-16" > < a href = "#cb6-16" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > eine eigene HiGHS-Kopie mit -> Symbolkonflikt).< / span > < / span >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< span id = "cb6-17" > < a href = "#cb6-17" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< span id = "cb6-18" > < a href = "#cb6-18" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Die Isolation besorgt ein ProcessPoolExecutor. Drei Einstellungen ergeben< / span > < / span >
< span id = "cb6-19" > < a href = "#cb6-19" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > zusammen die Garantie:< / span > < / span >
< span id = "cb6-20" > < a href = "#cb6-20" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-21" > < a href = "#cb6-21" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > mp_context " spawn" Der Kindprozess startet mit einem FRISCHEN< / span > < / span >
< span id = "cb6-22" > < a href = "#cb6-22" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Interpreter, statt den Speicher des Elternprozesses< / span > < / span >
< span id = "cb6-23" > < a href = "#cb6-23" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > zu erben. Was hier schon importiert ist, ist dort< / span > < / span >
< span id = "cb6-24" > < a href = "#cb6-24" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > nicht importiert. Mit dem Standard " fork" auf Linux< / span > < / span >
< span id = "cb6-25" > < a href = "#cb6-25" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > waere das nicht so.< / span > < / span >
< span id = "cb6-26" > < a href = "#cb6-26" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > max_tasks_per_child=1 Jede Aufgabe bekommt einen NEUEN Prozess. Ohne das< / span > < / span >
< span id = "cb6-27" > < a href = "#cb6-27" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > wuerde der Pool seinen Arbeiter wiederverwenden - und< / span > < / span >
< span id = "cb6-28" > < a href = "#cb6-28" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > beim zweiten Solver waere der Konflikt zurueck.< / span > < / span >
< span id = "cb6-29" > < a href = "#cb6-29" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > max_workers=1 Haelt die vier Laeufe nacheinander. Nicht aus< / span > < / span >
< span id = "cb6-30" > < a href = "#cb6-30" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Vorsicht, sondern damit die gemessenen Zeiten< / span > < / span >
< span id = "cb6-31" > < a href = "#cb6-31" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > vergleichbar bleiben.< / span > < / span >
< span id = "cb6-32" > < a href = "#cb6-32" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-33" > < a href = "#cb6-33" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Jeder Solver steht in einer eigenen Funktion mit LOKALEM Import. Das ist der< / span > < / span >
< span id = "cb6-34" > < a href = "#cb6-34" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Unterschied zu einem Codestring, den man an ' python -c' uebergibt: Die< / span > < / span >
< span id = "cb6-35" > < a href = "#cb6-35" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Funktion laesst sich einzeln aufrufen, testen und vom Editor pruefen - ein< / span > < / span >
< span id = "cb6-36" > < a href = "#cb6-36" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > String nicht.< / span > < / span >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< span id = "cb6-37" > < a href = "#cb6-37" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< span id = "cb6-38" > < a href = "#cb6-38" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Benoetigt: scipy, highspy, cvxpy, ortools< / span > < / span >
< span id = "cb6-39" > < a href = "#cb6-39" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb6-40" > < a href = "#cb6-40" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-41" > < a href = "#cb6-41" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > multiprocessing< / span >
< span id = "cb6-42" > < a href = "#cb6-42" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > time< / span >
< span id = "cb6-43" > < a href = "#cb6-43" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > concurrent.futures < span class = "im" > import< / span > ProcessPoolExecutor< / span >
< span id = "cb6-44" > < a href = "#cb6-44" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-45" > < a href = "#cb6-45" aria-hidden = "true" tabindex = "-1" > < / a > ERWARTET < span class = "op" > =< / span > < span class = "fl" > 530.0< / span > < span class = "co" > # Ergebnis der Handrechnung zum Produktionsprogramm< / span > < / span >
< span id = "cb6-46" > < a href = "#cb6-46" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-47" > < a href = "#cb6-47" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Die Instanz - einmal notiert, von allen vier Funktionen benutzt.< / span > < / span >
< span id = "cb6-48" > < a href = "#cb6-48" aria-hidden = "true" tabindex = "-1" > < / a > ZIEL < span class = "op" > =< / span > [< span class = "fl" > 10.0< / span > , < span class = "fl" > 15.0< / span > , < span class = "fl" > 25.0< / span > ]< / span >
< span id = "cb6-49" > < a href = "#cb6-49" aria-hidden = "true" tabindex = "-1" > < / a > MATRIX < span class = "op" > =< / span > [[< span class = "dv" > 1< / span > , < span class = "dv" > 1< / span > , < span class = "dv" > 2< / span > ], [< span class = "dv" > 2< / span > , < span class = "dv" > 3< / span > , < span class = "dv" > 1< / span > ]]< / span >
< span id = "cb6-50" > < a href = "#cb6-50" aria-hidden = "true" tabindex = "-1" > < / a > KAPAZITAET < span class = "op" > =< / span > [< span class = "fl" > 40.0< / span > , < span class = "fl" > 50.0< / span > ]< / span >
< span id = "cb6-51" > < a href = "#cb6-51" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-52" > < a href = "#cb6-52" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-53" > < a href = "#cb6-53" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_scipy() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ]]:< / span >
< span id = "cb6-54" > < a href = "#cb6-54" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > scipy.optimize < span class = "im" > import< / span > linprog< / span >
< span id = "cb6-55" > < a href = "#cb6-55" aria-hidden = "true" tabindex = "-1" > < / a > ergebnis < span class = "op" > =< / span > linprog(c< span class = "op" > =< / span > [< span class = "op" > -< / span > w < span class = "cf" > for< / span > w < span class = "kw" > in< / span > ZIEL], < span class = "co" > # linprog MINIMIERT -> negieren< / span > < / span >
< span id = "cb6-56" > < a href = "#cb6-56" aria-hidden = "true" tabindex = "-1" > < / a > A_ub< span class = "op" > =< / span > MATRIX, b_ub< span class = "op" > =< / span > KAPAZITAET,< / span >
< span id = "cb6-57" > < a href = "#cb6-57" aria-hidden = "true" tabindex = "-1" > < / a > bounds< span class = "op" > =< / span > [(< span class = "dv" > 0< / span > , < span class = "va" > None< / span > )] < span class = "op" > *< / span > < span class = "dv" > 3< / span > , method< span class = "op" > =< / span > < span class = "st" > " highs" < / span > )< / span >
< span id = "cb6-58" > < a href = "#cb6-58" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > < span class = "op" > -< / span > ergebnis.fun, < span class = "bu" > list< / span > (ergebnis.x)< / span >
< span id = "cb6-59" > < a href = "#cb6-59" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-60" > < a href = "#cb6-60" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-61" > < a href = "#cb6-61" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_highspy() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ]]:< / span >
< span id = "cb6-62" > < a href = "#cb6-62" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > highspy< / span >
< span id = "cb6-63" > < a href = "#cb6-63" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > numpy < span class = "im" > as< / span > np< / span >
< span id = "cb6-64" > < a href = "#cb6-64" aria-hidden = "true" tabindex = "-1" > < / a > h < span class = "op" > =< / span > highspy.Highs()< / span >
< span id = "cb6-65" > < a href = "#cb6-65" aria-hidden = "true" tabindex = "-1" > < / a > h.setOptionValue(< span class = "st" > " output_flag" < / span > , < span class = "va" > False< / span > )< / span >
< span id = "cb6-66" > < a href = "#cb6-66" aria-hidden = "true" tabindex = "-1" > < / a > h.addVars(< span class = "dv" > 3< / span > , np.zeros(< span class = "dv" > 3< / span > ), np.full(< span class = "dv" > 3< / span > , highspy.kHighsInf))< / span >
< span id = "cb6-67" > < a href = "#cb6-67" aria-hidden = "true" tabindex = "-1" > < / a > h.changeObjectiveSense(highspy.ObjSense.kMaximize)< / span >
< span id = "cb6-68" > < a href = "#cb6-68" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > j, wert < span class = "kw" > in< / span > < span class = "bu" > enumerate< / span > (ZIEL):< / span >
< span id = "cb6-69" > < a href = "#cb6-69" aria-hidden = "true" tabindex = "-1" > < / a > h.changeColCost(j, wert)< / span >
< span id = "cb6-70" > < a href = "#cb6-70" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # CSR-Format: starts[i] = Beginn von Zeile i in indices/values< / span > < / span >
< span id = "cb6-71" > < a href = "#cb6-71" aria-hidden = "true" tabindex = "-1" > < / a > h.addRows(< span class = "dv" > 2< / span > , np.full(< span class = "dv" > 2< / span > , < span class = "op" > -< / span > highspy.kHighsInf), np.array(KAPAZITAET), < span class = "dv" > 6< / span > ,< / span >
< span id = "cb6-72" > < a href = "#cb6-72" aria-hidden = "true" tabindex = "-1" > < / a > np.array([< span class = "dv" > 0< / span > , < span class = "dv" > 3< / span > ], dtype< span class = "op" > =< / span > np.int32),< / span >
< span id = "cb6-73" > < a href = "#cb6-73" aria-hidden = "true" tabindex = "-1" > < / a > np.array([< span class = "dv" > 0< / span > , < span class = "dv" > 1< / span > , < span class = "dv" > 2< / span > , < span class = "dv" > 0< / span > , < span class = "dv" > 1< / span > , < span class = "dv" > 2< / span > ], dtype< span class = "op" > =< / span > np.int32),< / span >
< span id = "cb6-74" > < a href = "#cb6-74" aria-hidden = "true" tabindex = "-1" > < / a > np.array([< span class = "bu" > float< / span > (w) < span class = "cf" > for< / span > zeile < span class = "kw" > in< / span > MATRIX < span class = "cf" > for< / span > w < span class = "kw" > in< / span > zeile]))< / span >
< span id = "cb6-75" > < a href = "#cb6-75" aria-hidden = "true" tabindex = "-1" > < / a > h.run()< / span >
< span id = "cb6-76" > < a href = "#cb6-76" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (h.getInfo().objective_function_value,< / span >
< span id = "cb6-77" > < a href = "#cb6-77" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > list< / span > (h.getSolution().col_value[:< span class = "dv" > 3< / span > ]))< / span >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< span id = "cb6-78" > < a href = "#cb6-78" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< span id = "cb6-79" > < a href = "#cb6-79" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-80" > < a href = "#cb6-80" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_cvxpy() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ]]:< / span >
< span id = "cb6-81" > < a href = "#cb6-81" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > cvxpy < span class = "im" > as< / span > cp< / span >
< span id = "cb6-82" > < a href = "#cb6-82" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > numpy < span class = "im" > as< / span > np< / span >
< span id = "cb6-83" > < a href = "#cb6-83" aria-hidden = "true" tabindex = "-1" > < / a > x < span class = "op" > =< / span > cp.Variable(< span class = "dv" > 3< / span > , nonneg< span class = "op" > =< / span > < span class = "va" > True< / span > )< / span >
< span id = "cb6-84" > < a href = "#cb6-84" aria-hidden = "true" tabindex = "-1" > < / a > problem < span class = "op" > =< / span > cp.Problem(cp.Maximize(np.array(ZIEL) < span class = "op" > @< / span > x),< / span >
< span id = "cb6-85" > < a href = "#cb6-85" aria-hidden = "true" tabindex = "-1" > < / a > [np.array(MATRIX) < span class = "op" > @< / span > x < span class = "op" > < =< / span > np.array(KAPAZITAET)])< / span >
< span id = "cb6-86" > < a href = "#cb6-86" aria-hidden = "true" tabindex = "-1" > < / a > problem.solve()< / span >
< span id = "cb6-87" > < a href = "#cb6-87" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > < span class = "bu" > float< / span > (problem.value), [< span class = "bu" > float< / span > (v) < span class = "cf" > for< / span > v < span class = "kw" > in< / span > x.value]< / span >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< span id = "cb6-88" > < a href = "#cb6-88" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-89" > < a href = "#cb6-89" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
< span id = "cb6-90" > < a href = "#cb6-90" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_ortools() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ]]:< / span >
< span id = "cb6-91" > < a href = "#cb6-91" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > ortools.linear_solver < span class = "im" > import< / span > pywraplp< / span >
< span id = "cb6-92" > < a href = "#cb6-92" aria-hidden = "true" tabindex = "-1" > < / a > s < span class = "op" > =< / span > pywraplp.Solver.CreateSolver(< span class = "st" > " GLOP" < / span > )< / span >
< span id = "cb6-93" > < a href = "#cb6-93" aria-hidden = "true" tabindex = "-1" > < / a > x < span class = "op" > =< / span > [s.NumVar(< span class = "dv" > 0< / span > , s.infinity(), < span class = "ss" > f" x< / span > < span class = "sc" > {< / span > j< span class = "op" > +< / span > < span class = "dv" > 1< / span > < span class = "sc" > }< / span > < span class = "ss" > " < / span > ) < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (< span class = "dv" > 3< / span > )]< / span >
< span id = "cb6-94" > < a href = "#cb6-94" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i, kapazitaet < span class = "kw" > in< / span > < span class = "bu" > enumerate< / span > (KAPAZITAET):< / span >
< span id = "cb6-95" > < a href = "#cb6-95" aria-hidden = "true" tabindex = "-1" > < / a > s.Add(< span class = "bu" > sum< / span > (MATRIX[i][j] < span class = "op" > *< / span > x[j] < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (< span class = "dv" > 3< / span > )) < span class = "op" > < =< / span > kapazitaet)< / span >
< span id = "cb6-96" > < a href = "#cb6-96" aria-hidden = "true" tabindex = "-1" > < / a > s.Maximize(< span class = "bu" > sum< / span > (ZIEL[j] < span class = "op" > *< / span > x[j] < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (< span class = "dv" > 3< / span > )))< / span >
< span id = "cb6-97" > < a href = "#cb6-97" aria-hidden = "true" tabindex = "-1" > < / a > s.Solve()< / span >
< span id = "cb6-98" > < a href = "#cb6-98" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > s.Objective().Value(), [v.solution_value() < span class = "cf" > for< / span > v < span class = "kw" > in< / span > x]< / span >
< span id = "cb6-99" > < a href = "#cb6-99" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-100" > < a href = "#cb6-100" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-101" > < a href = "#cb6-101" aria-hidden = "true" tabindex = "-1" > < / a > ANSAETZE < span class = "op" > =< / span > {< / span >
< span id = "cb6-102" > < a href = "#cb6-102" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " scipy.optimize.linprog" < / span > : loese_mit_scipy,< / span >
< span id = "cb6-103" > < a href = "#cb6-103" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " highspy (natives HiGHS)" < / span > : loese_mit_highspy,< / span >
< span id = "cb6-104" > < a href = "#cb6-104" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " cvxpy" < / span > : loese_mit_cvxpy,< / span >
< span id = "cb6-105" > < a href = "#cb6-105" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " ortools / GLOP" < / span > : loese_mit_ortools,< / span >
< span id = "cb6-106" > < a href = "#cb6-106" aria-hidden = "true" tabindex = "-1" > < / a > }< / span >
< span id = "cb6-107" > < a href = "#cb6-107" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-108" > < a href = "#cb6-108" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-109" > < a href = "#cb6-109" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > < span class = "va" > __name__< / span > < span class = "op" > ==< / span > < span class = "st" > " __main__" < / span > :< / span >
< span id = "cb6-110" > < a href = "#cb6-110" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 78< / span > )< / span >
< span id = "cb6-111" > < a href = "#cb6-111" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)" < / span > )< / span >
< span id = "cb6-112" > < a href = "#cb6-112" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 78< / span > )< / span >
< span id = "cb6-113" > < a href = "#cb6-113" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' Bibliothek' < / span > < span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Z*' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' x1' < / span > < span class = "sc" > :> 7}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' x2' < / span > < span class = "sc" > :> 7}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' x3' < / span > < span class = "sc" > :> 7}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Zeit' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb6-114" > < a href = "#cb6-114" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 78< / span > )< / span >
< span id = "cb6-115" > < a href = "#cb6-115" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-116" > < a href = "#cb6-116" aria-hidden = "true" tabindex = "-1" > < / a > werte < span class = "op" > =< / span > []< / span >
< span id = "cb6-117" > < a href = "#cb6-117" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Ein Pool, vier Aufgaben, vier frische Prozesse. Der Kontext muss< / span > < / span >
< span id = "cb6-118" > < a href = "#cb6-118" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # " spawn" sein - siehe Modulkommentar.< / span > < / span >
< span id = "cb6-119" > < a href = "#cb6-119" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > with< / span > ProcessPoolExecutor(< / span >
< span id = "cb6-120" > < a href = "#cb6-120" aria-hidden = "true" tabindex = "-1" > < / a > max_workers< span class = "op" > =< / span > < span class = "dv" > 1< / span > ,< / span >
< span id = "cb6-121" > < a href = "#cb6-121" aria-hidden = "true" tabindex = "-1" > < / a > mp_context< span class = "op" > =< / span > multiprocessing.get_context(< span class = "st" > " spawn" < / span > ),< / span >
< span id = "cb6-122" > < a href = "#cb6-122" aria-hidden = "true" tabindex = "-1" > < / a > max_tasks_per_child< span class = "op" > =< / span > < span class = "dv" > 1< / span > ) < span class = "im" > as< / span > pool:< / span >
< span id = "cb6-123" > < a href = "#cb6-123" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > name, funktion < span class = "kw" > in< / span > ANSAETZE.items():< / span >
< span id = "cb6-124" > < a href = "#cb6-124" aria-hidden = "true" tabindex = "-1" > < / a > beginn < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb6-125" > < a href = "#cb6-125" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > try< / span > :< / span >
< span id = "cb6-126" > < a href = "#cb6-126" aria-hidden = "true" tabindex = "-1" > < / a > wert, x < span class = "op" > =< / span > pool.submit(funktion).result(timeout< span class = "op" > =< / span > < span class = "dv" > 120< / span > )< / span >
< span id = "cb6-127" > < a href = "#cb6-127" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > except< / span > < span class = "pp" > Exception< / span > < span class = "im" > as< / span > fehler: < span class = "co" > # Bibliothek fehlt o. Ae.< / span > < / span >
< span id = "cb6-128" > < a href = "#cb6-128" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > name< span class = "sc" > :< 26}< / span > < span class = "ss" > nicht verfuegbar: < / span > < span class = "sc" > {< / span > < span class = "bu" > str< / span > (fehler)[:< span class = "dv" > 40< / span > ]< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb6-129" > < a href = "#cb6-129" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > continue< / span > < / span >
< span id = "cb6-130" > < a href = "#cb6-130" aria-hidden = "true" tabindex = "-1" > < / a > dauer < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > beginn< / span >
< span id = "cb6-131" > < a href = "#cb6-131" aria-hidden = "true" tabindex = "-1" > < / a > werte.append(wert)< / span >
< span id = "cb6-132" > < a href = "#cb6-132" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > name< span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > wert< span class = "sc" > :> 10.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > x[< span class = "dv" > 0< / span > ]< span class = "sc" > :> 7.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > x[< span class = "dv" > 1< / span > ]< span class = "sc" > :> 7.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > x[< span class = "dv" > 2< / span > ]< span class = "sc" > :> 7.2f}< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb6-133" > < a href = "#cb6-133" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > dauer< span class = "sc" > :> 8.2f}< / span > < span class = "ss" > s" < / span > )< / span >
< span id = "cb6-134" > < a href = "#cb6-134" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb6-135" > < a href = "#cb6-135" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 78< / span > )< / span >
< span id = "cb6-136" > < a href = "#cb6-136" aria-hidden = "true" tabindex = "-1" > < / a > spanne < span class = "op" > =< / span > < span class = "bu" > max< / span > (werte) < span class = "op" > -< / span > < span class = "bu" > min< / span > (werte)< / span >
< span id = "cb6-137" > < a href = "#cb6-137" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Spannweite zwischen den Bibliotheken: < / span > < span class = "sc" > {< / span > spanne< span class = "sc" > :.2e}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb6-138" > < a href = "#cb6-138" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Abweichung zur Handrechnung (< / span > < span class = "sc" > {< / span > ERWARTET< span class = "sc" > :.0f}< / span > < span class = "ss" > ): " < / span > < / span >
< span id = "cb6-139" > < a href = "#cb6-139" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "bu" > abs< / span > (werte[< span class = "dv" > 0< / span > ] < span class = "op" > -< / span > ERWARTET)< span class = "sc" > :.2e}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb6-140" > < a href = "#cb6-140" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > assert< / span > spanne < span class = "op" > < < / span > < span class = "fl" > 1e-6< / span > , < span class = "st" > " Die Bibliotheken widersprechen sich!" < / span > < / span >
< span id = "cb6-141" > < a href = "#cb6-141" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > assert< / span > < span class = "bu" > abs< / span > (werte[< span class = "dv" > 0< / span > ] < span class = "op" > -< / span > ERWARTET) < span class = "op" > < < / span > < span class = "fl" > 1e-6< / span > , < span class = "st" > " Ergebnis weicht von der Handrechnung ab!" < / span > < / span >
< span id = "cb6-142" > < a href = "#cb6-142" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Alle Wege fuehren zum selben, von Hand bestaetigten Optimum." < / span > )< / span >
< span id = "cb6-143" > < a href = "#cb6-143" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " (Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die" < / span > )< / span >
< span id = "cb6-144" > < a href = "#cb6-144" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " reine Solverleistung. Die Uebungsaufgabe ' Laufzeitvergleich' trennt beides.)" < / span > )< / span >
< span id = "cb6-145" > < a href = "#cb6-145" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 78< / span > )< / span > < / code > < / pre > < / div >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > < strong > Erwartete Ausgabe (Zeiten hardwareabhängig):< / strong > < / p >
< pre > < code > ==============================================================================
EIN SYSTEM - VIER ANSAETZE (je eigener Prozess)
==============================================================================
Bibliothek Z* x1 x2 x3 Zeit
------------------------------------------------------------------------------
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
scipy.optimize.linprog 530.00 0.00 12.00 14.00 0.59 s
highspy (natives HiGHS) 530.00 0.00 12.00 14.00 0.12 s
cvxpy 530.00 0.00 12.00 14.00 1.24 s
ortools / GLOP 530.00 0.00 12.00 14.00 0.33 s
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
------------------------------------------------------------------------------
Spannweite zwischen den Bibliotheken: 2.41e-08
Abweichung zur Handrechnung (530): 0.00e+00
Alle Wege fuehren zum selben, von Hand bestaetigten Optimum.
Phase 8.2: Solver-Isolation ohne subprocess-Codestrings
Setzt den Isolationsteil von Paket 1 aus Verbesserungen_02.md um. Der Plan
nannte zwei Programme; beim Suchen kam ein drittes dazu, das dasselbe Muster
verwendete.
Ein_System_Vier_Ansaetze.py und Benchmark_Skalierung.py hielten ihre vier
Solvervarianten als Zeichenketten in einem Dictionary und gaben sie an
"python -c" weiter - bei Benchmark_Skalierung.py sogar mit
.format()-Platzhaltern fuer die Instanzgroesse. Aus jeder Variante ist jetzt
eine gewoehnliche Funktion mit lokalem Import geworden.
Solverwechsel_CPSAT_HiGHS.py rief sich selbst ueber sys.argv erneut auf;
auch das entfaellt.
Ausgefuehrt wird ueber einen ProcessPoolExecutor mit zwei Einstellungen, die
beide noetig sind: mp_context "spawn" (frischer Interpreter statt geerbtem
Speicher - unter Linux ist fork der Standard) und max_tasks_per_child=1 (ein
neuer Prozess je Aufgabe; ohne das verwendet der Pool seinen Arbeiter
wieder, und beim zweiten Solver ist der Konflikt zurueck). Nachgemessen:
vier Aufgaben, vier verschiedene PIDs.
Der zweite Punkt hat einen eigenen Warnkasten bekommen, weil der Fehler
leicht zu machen und schwer zu finden ist: Der Absturz kaeme nicht beim
ersten Solver, sondern beim zweiten - und saehe aus wie ein Problem des
zweiten.
Regel 4, dreifach geprueft. Ein_System_Vier_Ansaetze.py: identisch bis auf
die Zeitspalte, einschliesslich der Spannweite 2,41e-08, auf die sich der
Merksatz des Kapitels beruft. Benchmark_Skalierung.py: alle zwoelf
Zielwerte und alle drei Spannweiten bitgleich; Zeiten und Speicher haben
sich verschoben, beide sind im Abdruck seit jeher als hardwareabhaengig
gekennzeichnet. Solverwechsel_CPSAT_HiGHS.py: Ausgabe ohne Zeiten
unveraendert.
Bewusst subprocess bleibt Mutationstest.py: Dort wird pytest auf einer
mutierten Kopie in einem temporaeren Verzeichnis gestartet - ein externes
Werkzeug auf veraenderten Dateien, nicht die Isolation eines Imports.
Neu im Kapitel Oekosystem: ein Abschnitt "Wie die Isolation aussieht, wenn
sie tragen soll" - warum ein Codestring die schlechteste Umsetzung von
"eigener Prozess" ist. Anhang C nennt jetzt ebenfalls ProcessPoolExecutor.
Ein eigener Fehler, gefunden und abgesichert: Ich hatte dem neuen ### ein
{#sec:...}-Label gegeben. ABSCHNITT_RE erkennt nur "## " - das Label waere
nie registriert worden und jeder Verweis darauf ins Leere gelaufen, ohne
Warnung. Label entfernt, --check meldet den Fall jetzt. Gegengetestet.
Stand: 818 Querverweise, 76 Programme, 33 pytest-Tests, PDF 760 Seiten, 69
netzfreie Programme fehlerfrei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:39:34 +02:00
(Die Zeiten enthalten Prozessstart und Import - sie messen NICHT die
reine Solverleistung. Die Uebungsaufgabe ' Laufzeitvergleich' trennt beides.)
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
==============================================================================< / code > < / pre >
< blockquote >
< p > < strong > 🎯 Merksatz zur Spannweite< / strong > Die vier Bibliotheken stimmen < strong > nicht auf die letzte Stelle< / strong > überein, sondern nur bis auf < span class = "math inline" > 2{,}4 \times 10^{-8}< / span > . Das ist normal: Solver arbeiten mit endlicher Genauigkeit und brechen ab, sobald ihre eigene Toleranz erreicht ist. < strong > Vergleichen Sie Solver-Ergebnisse deshalb nie mit < code > ==< / code > < / strong > , sondern immer mit einer Toleranz — < code > abs(a - b) < 1e-6< / code > oder < code > np.isclose()< / code > . Wer auf exakte Gleichheit prüft, baut sich Tests, die zufällig mal bestehen und mal nicht.< / p >
< / blockquote >
< blockquote >
< p > < strong > 💻 Code-Durchgang< / strong > < / p >
< table >
< colgroup >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Ansatz< / th >
< th > Zeilen für das Modell< / th >
< th > Charakter< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > linprog< / code > < / td >
< td > 3< / td >
< td > Matrizen direkt übergeben. Kürzeste Variante, aber man muss selbst negieren und die Matrixform von Hand herstellen.< / td >
< / tr >
< tr class = "even" >
< td > < code > highspy< / code > < / td >
< td > ~15< / td >
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.
2026-09-10 17:40:29 +02:00
< td > Alles explizit, inklusive < a href = "anhang-glossar.md#gloss:csr-format" class = "glossar-link" > CSR-Format< / a > der dünnbesetzten Matrix. Aufwendig — dafür volle Kontrolle und kein Overhead beim wiederholten Lösen.< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / tr >
< tr class = "odd" >
< td > < code > cvxpy< / code > < / td >
< td > 4< / td >
< td > Liest sich wie die mathematische Formulierung. Prüft zusätzlich automatisch, ob das Problem konvex ist. Höchster Startaufwand pro Lauf (Kompilierung des Ausdrucksbaums).< / td >
< / tr >
< tr class = "even" >
< td > < code > ortools< / code > /GLOP< / td >
< td > ~6< / td >
< td > Bedingungen einzeln mit < code > Add()< / code > — gut lesbar bei wenigen, mühsam bei vielen Restriktionen.< / td >
< / tr >
< / tbody >
< / table >
< p > < strong > Der wichtigste Teil des Programms sind die letzten fünf Zeilen:< / strong > der Kreuzvergleich. Vier unabhängige Implementierungen, die auf 13 Nachkommastellen übereinstimmen und mit einer Handrechnung zusammenpassen, sind ein starkes Indiz für Korrektheit. Bei einem einzelnen Solver-Ergebnis haben Sie diese Sicherheit nicht.< / p >
< / blockquote >
< h3 id = "was-das-csr-format-bedeutet" > Was das CSR-Format bedeutet< / h3 >
< p > < code > highspy< / code > erwartet die Nebenbedingungsmatrix im < strong > CSR-Format< / strong > (< em > Compressed Sparse Row< / em > , komprimierte Zeilendarstellung). Statt der vollen Matrix speichert man nur die Einträge ungleich null:< / p >
< p > < span class = "math display" >
\mathbf{A} = \begin{pmatrix} 1 & 1 & 2 \\ 2 & 3 & 1 \end{pmatrix}
< / span > < / p >
< table >
< colgroup >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Array< / th >
< th > Inhalt< / th >
< th > Bedeutung< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > values< / code > < / td >
< td > < code > [1, 1, 2, 2, 3, 1]< / code > < / td >
< td > die Zahlen selbst, zeilenweise< / td >
< / tr >
< tr class = "even" >
< td > < code > indices< / code > < / td >
< td > < code > [0, 1, 2, 0, 1, 2]< / code > < / td >
< td > zu welcher < strong > Spalte< / strong > gehört jeder Wert< / td >
< / tr >
< tr class = "odd" >
< td > < code > starts< / code > < / td >
< td > < code > [0, 3]< / code > < / td >
< td > Zeile 0 beginnt bei Position 0, Zeile 1 bei Position 3< / td >
< / tr >
< / tbody >
< / table >
< p > Bei kleinen Modellen wirkt das umständlich. Bei realen Modellen mit 100 000 Variablen und nur 0,1 % Nicht-Null-Einträgen spart es Faktor 1000 an Speicher — und ist der Grund, warum große LPs überhaupt lösbar sind.< / p >
< blockquote >
< p > < strong > ⚠️ Typische Fehler< / strong > < / p >
< ul >
< li > < strong > Vergessen, dass < code > linprog< / code > minimiert.< / strong > Der häufigste Fehler überhaupt. Symptom: Der „optimale“ Gewinn ist erstaunlich niedrig oder null.< / li >
< li > < strong > CVXPY für ein nicht-konvexes Problem verwenden.< / strong > CVXPY lehnt das ab mit < code > DCPError: Problem does not follow DCP rules< / code > . Das ist ein < strong > Feature< / strong > , keine Einschränkung: Der Fehler sagt Ihnen, dass Ihre Formulierung keine Optimalitätsgarantie hätte.< / li >
< li > < strong > CP-SAT mit kontinuierlichen Variablen füttern.< / strong > CP-SAT kennt nur ganze Zahlen. Wer Euro-Beträge modelliert, rechnet in Cent (Ganzzahl) — oder nimmt einen LP-Solver.< / li >
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.
2026-09-10 17:40:29 +02:00
< li > < strong > Für jeden Lauf ein neues Modell bauen.< / strong > Bei 60 < a href = "anhang-glossar.md#gloss:backtest" class = "glossar-link" > Backtest< / a > -Rebalancings kostet das Aufbauen mehr Zeit als das Lösen. < code > highspy< / code > und CVXPY-< code > Parameter< / code > erlauben es, das Modell einmal zu bauen und nur Daten zu tauschen (siehe < a href = "markowitz.html#kap-markowitz" > Kapitel 19< / a > und < a href = "handelsmaschine.html#kap-handelsmaschine" > Kapitel 21< / a > ).< / li >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / ul >
< / blockquote >
< hr / >
< h2 id = "sec:oekosystem-wann-lohnt-sich-welche-ebene" > 3.6 Wann lohnt sich welche Ebene?< / h2 >
< table >
< colgroup >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Situation< / th >
< th > Empfehlung< / th >
< th > Begründung< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > Einmaliges kleines LP, Prototyp< / td >
< td > < code > scipy.optimize.linprog< / code > < / td >
< td > Keine zusätzliche Abhängigkeit, 3 Zeilen< / td >
< / tr >
< tr class = "even" >
< td > Portfolio, Risiko, alles Konvexe< / td >
< td > < strong > CVXPY< / strong > < / td >
< td > Lesbarkeit + automatische Konvexitätsprüfung< / td >
< / tr >
< tr class = "odd" >
< td > Dienstpläne, Zuordnung, Reihenfolge< / td >
< td > < strong > CP-SAT< / strong > < / td >
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.
2026-09-10 17:40:29 +02:00
< td > < a href = "anhang-glossar.md#gloss:globale-constraints" class = "glossar-link" > Globale Constraints< / a > , Konfliktlernen< / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / tr >
< tr class = "even" >
< td > Fahrzeugtouren< / td >
< td > < strong > OR-Tools Routing< / strong > < / td >
< td > Fertige Metaheuristiken, jahrzehntelang optimiert< / td >
< / tr >
< tr class = "odd" >
< td > MILP mit Fixkosten, Kardinalität< / td >
< td > < code > highspy< / code > oder CP-SAT< / td >
< td > Branch-and-Cut auf ökonomischer Struktur< / td >
< / tr >
< tr class = "even" >
< td > 50 000+ Variablen, wiederholte Läufe< / td >
< td > < code > highspy< / code > oder Pyomo< / td >
< td > Modellaufbau wird sonst zum Engpass< / td >
< / tr >
< tr class = "odd" >
Glossar: Sprungleiste, Abkürzungen ausschreiben, CLAUDE.md-Regel
Sprungleiste (A B C ... Z) im Glossar — wie im Stichwortverzeichnis:
ein Klick je Buchstabe springt zur '## <Buchstabe>'-Überschrift.
Eingebaut in erzeuge_glossar_04.py als {=html}-Rohblock (nur HTML,
im PDF bedeutungslos).
Abkürzungen beim ersten Mal ausschreiben: 'VaR' → 'VaR (Value at Risk)',
'EVPI' → 'EVPI (Expected Value of Perfect Information)' als Link-Text
bei der ersten Nennung je Datei; danach nur die Abkürzung. 19 Abkürzungen
erfasst (VaR, CVaR, KKT, NLP, MILP, EVPI, LP, QP, ...).
CLAUDE.md: Regel 'Glossar-Links: Fachbegriffe im Fließtext automatisch
verlinkt' dokumentiert — resolve_glossar(), LINK_TEXTE,
ABBREV_ERSTE_NENNUNG, _NICHT_AUTO_LINKEN, _ZU_KURZ_ABK, .glossar-link,
{gl:}-Marke, Sprungleiste, {#gloss:<slug>}-Anchor.
2026-09-10 17:52:49 +02:00
< td > Nichtkonvexes < a href = "anhang-glossar.md#gloss:nichtlineare-programmierung-nlp" class = "glossar-link" > NLP (Nichtlineare Programmierung)< / a > < / td >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< td > < code > scipy.optimize.minimize< / code > < / td >
< td > Bewusst mit mehreren Startpunkten arbeiten< / td >
< / tr >
< / tbody >
< / table >
< blockquote >
< p > < strong > 🎯 Merksatz< / strong > Wählen Sie den Solver nach der < strong > Struktur des Modells< / strong > , nicht nach Gewohnheit. Ein Zuweisungsproblem in CVXPY zu quälen oder ein Portfolio mit CP-SAT nachzubauen kostet Laufzeit und Nerven — und meist auch Lösungsqualität.< / p >
< / blockquote >
< hr / >
< h2 id = "sec:oekosystem-pyomo-linopy" > 3.7 Modellierungsschichten für große Modelle: Pyomo und Linopy< / h2 >
< p > Die vier Bibliotheken aus dem Vierfach-Vergleich decken den Alltag weitgehend ab. Sobald Modelle industrielle Größe erreichen — zehntausende Variablen, Daten aus mehreren Systemen, mehrere Jahre Lebensdauer — treten zwei weitere Werkzeuge in den Vordergrund.< / p >
< h3 id = "pyomo-die-algebraische-denkweise" > Pyomo: die algebraische Denkweise< / h3 >
< p > < strong > Pyomo< / strong > ist im deutschsprachigen Raum der De-facto-Standard für große LP- und MILP-Modelle in Energiewirtschaft, Chemie und Logistik. Sein Kennzeichen: Es denkt in < strong > Mengen und Indizes< / strong > , so wie die mathematische Formulierung selbst.< / p >
< p > Der entscheidende Satz ist dieser:< / 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 > modell.kapazitaet < span class = "op" > =< / span > pyo.Constraint(modell.R, rule< span class = "op" > =< / span > kapazitaet)< / span > < / code > < / pre > < / div >
< p > Das ist das < span class = "math inline" > \forall i \in I< / span > aus < a href = "einfuehrung.html#kap-einfuehrung" > Kapitel 1< / a > , unmittelbar in Code übersetzt: eine Regel, angewandt auf jedes Element einer Menge. Pyomo kann außerdem, was CVXPY und OR-Tools nicht können — < strong > nichtlineare und gemischt-ganzzahlig-nichtlineare< / strong > Modelle (MINLP) an Solver wie Ipopt oder BONMIN übergeben (siehe < a href = "qp-nlp.html#kap-qp-nlp" > Kapitel 11< / a > ).< / p >
< h3 id = "linopy-eine-zeile-zehntausend-nebenbedingungen" > Linopy: eine Zeile, zehntausend Nebenbedingungen< / h3 >
< p > < strong > Linopy< / strong > verfolgt einen anderen Ansatz: Variablen sind < strong > beschriftete Arrays< / strong > (< code > xarray< / code > ), keine indizierten Einzelobjekte. Damit wird aus< / p >
< div class = "sourceCode" id = "cb9" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb9-1" > < a href = "#cb9-1" aria-hidden = "true" tabindex = "-1" > < / a > modell.add_constraints((verbrauch < span class = "op" > *< / span > x).< span class = "bu" > sum< / span > (< span class = "st" > " produkt" < / span > ) < span class = "op" > < =< / span > vorrat, name< span class = "op" > =< / span > < span class = "st" > " kapazitaet" < / span > )< / span > < / code > < / pre > < / div >
< p > nicht eine Nebenbedingung, sondern < strong > so viele, wie die Achse < code > ressource< / code > Einträge hat< / strong > — erzeugt als Matrixoperation, ohne dass je eine Python-Schleife läuft. In Energiesystem- und Netzmodellen mit den Achsen < em > Region × Technologie × Stunde des Jahres< / em > ist das der Unterschied zwischen Minuten und Sekunden beim Modellaufbau.< / p >
< h3 id = "die-schichten-im-überblick" > Die Schichten im Überblick< / h3 >
< table >
< colgroup >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< col style = "width: 25%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Schicht< / th >
< th > Denkweise< / th >
< th > Stärke< / th >
< th > Grenze< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < code > scipy.optimize< / code > < / td >
< td > rohe Matrizen< / td >
< td > keine Zusatzabhängigkeit, minimaler Start< / td >
< td > Vorzeichen und Matrixform von Hand; nur LP< / td >
< / tr >
< tr class = "even" >
< td > < code > highspy< / code > < / td >
< td > rohe Matrizen, volle Solversteuerung< / td >
< td > schnellster wiederholter Aufbau, alle HiGHS-Optionen< / td >
< td > CSR-Format selbst herstellen< / td >
< / tr >
< tr class = "odd" >
< td > < code > ortools< / code > < / td >
< td > Objekte, Bedingung für Bedingung< / td >
< td > CP-SAT und Routing; sehr gut lesbar< / td >
< td > Aufbau wird bei < span class = "math inline" > 10^5< / span > Variablen zum Engpass< / td >
< / tr >
< tr class = "even" >
< td > < code > cvxpy< / code > < / td >
< td > mathematiknahe Ausdrücke< / td >
< td > automatische Konvexitätsprüfung, Risikomodelle< / td >
< td > keine Ganzzahligkeit in großem Stil; Kompilierung kostet Zeit< / td >
< / tr >
< tr class = "odd" >
< td > < strong > < code > Pyomo< / code > < / strong > < / td >
< td > Mengen und Indizes< / td >
< td > Industriestandard, Daten/Modell getrennt, MINLP-fähig< / td >
< td > mehr Zeremonie; eigene Lernkurve< / td >
< / tr >
< tr class = "even" >
< td > < strong > < code > Linopy< / code > < / strong > < / td >
< td > beschriftete Arrays (xarray)< / td >
< td > extrem schneller Aufbau bei Millionen Nebenbedingungen< / td >
< td > nur LP/MILP; Daten müssen zu Arrays passen< / td >
< / tr >
< / tbody >
< / table >
< blockquote >
< p > < strong > 🎯 Merksatz< / strong > Keine dieser Schichten rechnet selbst. Alle sechs geben dasselbe Modell am Ende an dieselbe Handvoll C++-Solver weiter — hier fast immer an HiGHS. Die Wahl der Schicht entscheidet über < strong > Ihre< / strong > Produktivität, nicht über die des Rechners.< / p >
< / blockquote >
< p > Das folgende Programm löst mit beiden Schichten dasselbe Produktionsproblem wie der Vierfach-Vergleich und prüft das Ergebnis gegen dieselbe Handrechnung.< / p >
< div class = "sourceCode" id = "cb10" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb10-1" > < a href = "#cb10-1" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > #!/usr/bin/env python3< / span > < / span >
< span id = "cb10-2" > < a href = "#cb10-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-3" > < a href = "#cb10-3" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Modellierungsschichten.py< / span > < / span >
< span id = "cb10-4" > < a href = "#cb10-4" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb10-5" > < a href = "#cb10-5" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Kapitel Oekosystem: Pyomo und Linopy - zwei Modellierungsschichten fuer grosse Modelle.< / span > < / span >
< span id = "cb10-6" > < a href = "#cb10-6" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-7" > < a href = "#cb10-7" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Geloest wird dasselbe Produktionsproblem wie im Vierfach-Vergleich:< / span > < / span >
< span id = "cb10-8" > < a href = "#cb10-8" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-9" > < a href = "#cb10-9" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > max 10*x1 + 15*x2 + 25*x3< / span > < / span >
< span id = "cb10-10" > < a href = "#cb10-10" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > u.d.N. x1 + x2 + 2*x3 < = 40< / span > < / span >
< span id = "cb10-11" > < a href = "#cb10-11" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > 2*x1 + 3*x2 + x3 < = 50< / span > < / span >
< span id = "cb10-12" > < a href = "#cb10-12" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > x > = 0< / span > < / span >
< span id = "cb10-13" > < a href = "#cb10-13" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-14" > < a href = "#cb10-14" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Handrechnung: Z* = 530 bei x = (0, 12, 14).< / span > < / span >
< span id = "cb10-15" > < a href = "#cb10-15" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-16" > < a href = "#cb10-16" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Der Vergleich zeigt die beiden Denkweisen:< / span > < / span >
< span id = "cb10-17" > < a href = "#cb10-17" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > * Pyomo - algebraisch, indexbasiert, Industriestandard fuer Grossmodelle,< / span > < / span >
< span id = "cb10-18" > < a href = "#cb10-18" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > trennt Modellstruktur sauber von den Daten (AbstractModel).< / span > < / span >
< span id = "cb10-19" > < a href = "#cb10-19" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > * Linopy - beschriftete Arrays (xarray): eine Zeile Code erzeugt Tausende< / span > < / span >
< span id = "cb10-20" > < a href = "#cb10-20" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Nebenbedingungen auf einmal, ohne Python-Schleife.< / span > < / span >
< span id = "cb10-21" > < a href = "#cb10-21" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-22" > < a href = "#cb10-22" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Beide bringen KEINEN eigenen Solver mit; hier rechnet in beiden Faellen HiGHS.< / span > < / span >
< span id = "cb10-23" > < a href = "#cb10-23" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-24" > < a href = "#cb10-24" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > WICHTIG: ortools wird in diesem Programm bewusst NICHT importiert - es< / span > < / span >
< span id = "cb10-25" > < a href = "#cb10-25" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > vertraegt sich nicht mit der HiGHS-Kopie, die Pyomo und Linopy laden< / span > < / span >
< span id = "cb10-26" > < a href = "#cb10-26" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > (siehe die Stolperfalle im Abschnitt ' Ein System - vier Programmieransaetze' ).< / span > < / span >
< span id = "cb10-27" > < a href = "#cb10-27" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-28" > < a href = "#cb10-28" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Benoetigt: pyomo, linopy, xarray, pandas, highspy, numpy< / span > < / span >
< span id = "cb10-29" > < a href = "#cb10-29" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb10-30" > < a href = "#cb10-30" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-31" > < a href = "#cb10-31" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > __future__ < span class = "im" > import< / span > annotations< / span >
< span id = "cb10-32" > < a href = "#cb10-32" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-33" > < a href = "#cb10-33" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > time< / span >
< span id = "cb10-34" > < a href = "#cb10-34" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-35" > < a href = "#cb10-35" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > numpy < span class = "im" > as< / span > np< / span >
< span id = "cb10-36" > < a href = "#cb10-36" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > pandas < span class = "im" > as< / span > pd< / span >
< span id = "cb10-37" > < a href = "#cb10-37" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > pyomo.environ < span class = "im" > as< / span > pyo< / span >
< span id = "cb10-38" > < a href = "#cb10-38" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > xarray < span class = "im" > as< / span > xr< / span >
< span id = "cb10-39" > < a href = "#cb10-39" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > linopy< / span >
< span id = "cb10-40" > < a href = "#cb10-40" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-41" > < a href = "#cb10-41" aria-hidden = "true" tabindex = "-1" > < / a > ERWARTET < span class = "op" > =< / span > < span class = "fl" > 530.0< / span > < span class = "co" > # Ergebnis der Handrechnung< / span > < / span >
< span id = "cb10-42" > < a href = "#cb10-42" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-43" > < a href = "#cb10-43" aria-hidden = "true" tabindex = "-1" > < / a > PRODUKTE < span class = "op" > =< / span > [< span class = "st" > " Standard" < / span > , < span class = "st" > " Komfort" < / span > , < span class = "st" > " Premium" < / span > ]< / span >
< span id = "cb10-44" > < a href = "#cb10-44" aria-hidden = "true" tabindex = "-1" > < / a > RESSOURCEN < span class = "op" > =< / span > [< span class = "st" > " Material" < / span > , < span class = "st" > " Montage" < / span > ]< / span >
< span id = "cb10-45" > < a href = "#cb10-45" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-46" > < a href = "#cb10-46" aria-hidden = "true" tabindex = "-1" > < / a > DECKUNGSBEITRAG < span class = "op" > =< / span > np.array([< span class = "fl" > 10.0< / span > , < span class = "fl" > 15.0< / span > , < span class = "fl" > 25.0< / span > ])< / span >
< span id = "cb10-47" > < a href = "#cb10-47" aria-hidden = "true" tabindex = "-1" > < / a > VERBRAUCH < span class = "op" > =< / span > np.array([[< span class = "fl" > 1.0< / span > , < span class = "fl" > 1.0< / span > , < span class = "fl" > 2.0< / span > ], < span class = "co" > # Material je Produkt< / span > < / span >
< span id = "cb10-48" > < a href = "#cb10-48" aria-hidden = "true" tabindex = "-1" > < / a > [< span class = "fl" > 2.0< / span > , < span class = "fl" > 3.0< / span > , < span class = "fl" > 1.0< / span > ]]) < span class = "co" > # Montage je Produkt< / span > < / span >
< span id = "cb10-49" > < a href = "#cb10-49" aria-hidden = "true" tabindex = "-1" > < / a > VORRAT < span class = "op" > =< / span > np.array([< span class = "fl" > 40.0< / span > , < span class = "fl" > 50.0< / span > ])< / span >
< span id = "cb10-50" > < a href = "#cb10-50" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-51" > < a href = "#cb10-51" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-52" > < a href = "#cb10-52" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # --- Pyomo: algebraisch und indexbasiert ------------------------------------< / span > < / span >
< span id = "cb10-53" > < a href = "#cb10-53" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-54" > < a href = "#cb10-54" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_pyomo() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ], < span class = "bu" > float< / span > ]:< / span >
< span id = "cb10-55" > < a href = "#cb10-55" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Pyomo denkt in Mengen und Indizes, wie ein Mathematiker es aufschreibt.< / span > < / span >
< span id = "cb10-56" > < a href = "#cb10-56" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-57" > < a href = "#cb10-57" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > `Constraint(RESSOURCEN, rule=...)` erzeugt fuer JEDES Element der Menge< / span > < / span >
< span id = "cb10-58" > < a href = "#cb10-58" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > eine Nebenbedingung - das ist das ' fuer alle i' der Formelsprache,< / span > < / span >
< span id = "cb10-59" > < a href = "#cb10-59" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > unmittelbar in Code uebersetzt.< / span > < / span >
< span id = "cb10-60" > < a href = "#cb10-60" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb10-61" > < a href = "#cb10-61" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb10-62" > < a href = "#cb10-62" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-63" > < a href = "#cb10-63" aria-hidden = "true" tabindex = "-1" > < / a > modell < span class = "op" > =< / span > pyo.ConcreteModel(name< span class = "op" > =< / span > < span class = "st" > " Produktionsprogramm" < / span > )< / span >
< span id = "cb10-64" > < a href = "#cb10-64" aria-hidden = "true" tabindex = "-1" > < / a > modell.P < span class = "op" > =< / span > pyo.Set(initialize< span class = "op" > =< / span > PRODUKTE)< / span >
< span id = "cb10-65" > < a href = "#cb10-65" aria-hidden = "true" tabindex = "-1" > < / a > modell.R < span class = "op" > =< / span > pyo.Set(initialize< span class = "op" > =< / span > RESSOURCEN)< / span >
< span id = "cb10-66" > < a href = "#cb10-66" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-67" > < a href = "#cb10-67" aria-hidden = "true" tabindex = "-1" > < / a > modell.db < span class = "op" > =< / span > pyo.Param(modell.P, initialize< span class = "op" > =< / span > < span class = "bu" > dict< / span > (< span class = "bu" > zip< / span > (PRODUKTE, DECKUNGSBEITRAG)))< / span >
< span id = "cb10-68" > < a href = "#cb10-68" aria-hidden = "true" tabindex = "-1" > < / a > modell.a < span class = "op" > =< / span > pyo.Param(modell.R, modell.P, initialize< span class = "op" > =< / span > {< / span >
< span id = "cb10-69" > < a href = "#cb10-69" aria-hidden = "true" tabindex = "-1" > < / a > (r, p): VERBRAUCH[i, j]< / span >
< span id = "cb10-70" > < a href = "#cb10-70" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i, r < span class = "kw" > in< / span > < span class = "bu" > enumerate< / span > (RESSOURCEN) < span class = "cf" > for< / span > j, p < span class = "kw" > in< / span > < span class = "bu" > enumerate< / span > (PRODUKTE)})< / span >
< span id = "cb10-71" > < a href = "#cb10-71" aria-hidden = "true" tabindex = "-1" > < / a > modell.vorrat < span class = "op" > =< / span > pyo.Param(modell.R, initialize< span class = "op" > =< / span > < span class = "bu" > dict< / span > (< span class = "bu" > zip< / span > (RESSOURCEN, VORRAT)))< / span >
< span id = "cb10-72" > < a href = "#cb10-72" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-73" > < a href = "#cb10-73" aria-hidden = "true" tabindex = "-1" > < / a > modell.x < span class = "op" > =< / span > pyo.Var(modell.P, domain< span class = "op" > =< / span > pyo.NonNegativeReals)< / span >
< span id = "cb10-74" > < a href = "#cb10-74" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-75" > < a href = "#cb10-75" aria-hidden = "true" tabindex = "-1" > < / a > modell.ziel < span class = "op" > =< / span > pyo.Objective(< / span >
< span id = "cb10-76" > < a href = "#cb10-76" aria-hidden = "true" tabindex = "-1" > < / a > expr< span class = "op" > =< / span > < span class = "bu" > sum< / span > (modell.db[p] < span class = "op" > *< / span > modell.x[p] < span class = "cf" > for< / span > p < span class = "kw" > in< / span > modell.P),< / span >
< span id = "cb10-77" > < a href = "#cb10-77" aria-hidden = "true" tabindex = "-1" > < / a > sense< span class = "op" > =< / span > pyo.maximize)< / span >
< span id = "cb10-78" > < a href = "#cb10-78" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-79" > < a href = "#cb10-79" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > kapazitaet(m, r):< / span >
< span id = "cb10-80" > < a href = "#cb10-80" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > < span class = "bu" > sum< / span > (m.a[r, p] < span class = "op" > *< / span > m.x[p] < span class = "cf" > for< / span > p < span class = "kw" > in< / span > m.P) < span class = "op" > < =< / span > m.vorrat[r]< / span >
< span id = "cb10-81" > < a href = "#cb10-81" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-82" > < a href = "#cb10-82" aria-hidden = "true" tabindex = "-1" > < / a > modell.kapazitaet < span class = "op" > =< / span > pyo.Constraint(modell.R, rule< span class = "op" > =< / span > kapazitaet)< / span >
< span id = "cb10-83" > < a href = "#cb10-83" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-84" > < a href = "#cb10-84" aria-hidden = "true" tabindex = "-1" > < / a > ergebnis < span class = "op" > =< / span > pyo.SolverFactory(< span class = "st" > " appsi_highs" < / span > ).solve(modell)< / span >
< span id = "cb10-85" > < a href = "#cb10-85" aria-hidden = "true" tabindex = "-1" > < / a > dauer < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb10-86" > < a href = "#cb10-86" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-87" > < a href = "#cb10-87" aria-hidden = "true" tabindex = "-1" > < / a > status < span class = "op" > =< / span > ergebnis.solver.termination_condition< / span >
< span id = "cb10-88" > < a href = "#cb10-88" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > status < span class = "op" > !=< / span > pyo.TerminationCondition.optimal:< / span >
< span id = "cb10-89" > < a href = "#cb10-89" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > raise< / span > < span class = "pp" > RuntimeError< / span > (< span class = "ss" > f" Pyomo meldet Status: < / span > < span class = "sc" > {< / span > status< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb10-90" > < a href = "#cb10-90" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-91" > < a href = "#cb10-91" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "bu" > float< / span > (pyo.value(modell.ziel)),< / span >
< span id = "cb10-92" > < a href = "#cb10-92" aria-hidden = "true" tabindex = "-1" > < / a > [< span class = "bu" > float< / span > (pyo.value(modell.x[p])) < span class = "cf" > for< / span > p < span class = "kw" > in< / span > PRODUKTE],< / span >
< span id = "cb10-93" > < a href = "#cb10-93" aria-hidden = "true" tabindex = "-1" > < / a > dauer)< / span >
< span id = "cb10-94" > < a href = "#cb10-94" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-95" > < a href = "#cb10-95" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-96" > < a href = "#cb10-96" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # --- Linopy: beschriftete Arrays --------------------------------------------< / span > < / span >
< span id = "cb10-97" > < a href = "#cb10-97" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-98" > < a href = "#cb10-98" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_linopy() < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ], < span class = "bu" > float< / span > ]:< / span >
< span id = "cb10-99" > < a href = "#cb10-99" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Linopy denkt in beschrifteten Arrays (xarray).< / span > < / span >
< span id = "cb10-100" > < a href = "#cb10-100" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-101" > < a href = "#cb10-101" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Der entscheidende Unterschied: `(verbrauch * x).sum(" produkt" ) < = vorrat`< / span > < / span >
< span id = "cb10-102" > < a href = "#cb10-102" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > ist EINE Zeile und erzeugt so viele Nebenbedingungen, wie die Dimension< / span > < / span >
< span id = "cb10-103" > < a href = "#cb10-103" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > ' ressource' Eintraege hat. Bei 2 Ressourcen faellt das nicht auf, bei< / span > < / span >
< span id = "cb10-104" > < a href = "#cb10-104" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > 200 000 schon - dort entstehen sie als Matrixoperation statt in einer< / span > < / span >
< span id = "cb10-105" > < a href = "#cb10-105" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Python-Schleife.< / span > < / span >
< span id = "cb10-106" > < a href = "#cb10-106" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb10-107" > < a href = "#cb10-107" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb10-108" > < a href = "#cb10-108" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-109" > < a href = "#cb10-109" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Benannte Indizes statt blosser Listen: Dadurch heissen die Achsen< / span > < / span >
< span id = "cb10-110" > < a href = "#cb10-110" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # ' produkt' und ' ressource' , und xarray fuehrt sie beim Rechnen von allein< / span > < / span >
< span id = "cb10-111" > < a href = "#cb10-111" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # richtig zusammen. Ohne Namen vergibt linopy ' dim_0' , und man muss< / span > < / span >
< span id = "cb10-112" > < a href = "#cb10-112" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # spaeter umbenennen - eine haeufige Stolperstelle.< / span > < / span >
< span id = "cb10-113" > < a href = "#cb10-113" aria-hidden = "true" tabindex = "-1" > < / a > produkt < span class = "op" > =< / span > pd.Index(PRODUKTE, name< span class = "op" > =< / span > < span class = "st" > " produkt" < / span > )< / span >
< span id = "cb10-114" > < a href = "#cb10-114" aria-hidden = "true" tabindex = "-1" > < / a > ressource < span class = "op" > =< / span > pd.Index(RESSOURCEN, name< span class = "op" > =< / span > < span class = "st" > " ressource" < / span > )< / span >
< span id = "cb10-115" > < a href = "#cb10-115" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-116" > < a href = "#cb10-116" aria-hidden = "true" tabindex = "-1" > < / a > modell < span class = "op" > =< / span > linopy.Model()< / span >
< span id = "cb10-117" > < a href = "#cb10-117" aria-hidden = "true" tabindex = "-1" > < / a > modell.add_variables(lower< span class = "op" > =< / span > < span class = "dv" > 0< / span > , coords< span class = "op" > =< / span > [produkt], name< span class = "op" > =< / span > < span class = "st" > " menge" < / span > )< / span >
< span id = "cb10-118" > < a href = "#cb10-118" aria-hidden = "true" tabindex = "-1" > < / a > x < span class = "op" > =< / span > modell.variables[< span class = "st" > " menge" < / span > ]< / span >
< span id = "cb10-119" > < a href = "#cb10-119" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-120" > < a href = "#cb10-120" aria-hidden = "true" tabindex = "-1" > < / a > db < span class = "op" > =< / span > xr.DataArray(DECKUNGSBEITRAG, coords< span class = "op" > =< / span > [produkt])< / span >
< span id = "cb10-121" > < a href = "#cb10-121" aria-hidden = "true" tabindex = "-1" > < / a > verbrauch < span class = "op" > =< / span > xr.DataArray(VERBRAUCH, coords< span class = "op" > =< / span > [ressource, produkt])< / span >
< span id = "cb10-122" > < a href = "#cb10-122" aria-hidden = "true" tabindex = "-1" > < / a > vorrat < span class = "op" > =< / span > xr.DataArray(VORRAT, coords< span class = "op" > =< / span > [ressource])< / span >
< span id = "cb10-123" > < a href = "#cb10-123" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-124" > < a href = "#cb10-124" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # EINE Zeile - sie erzeugt so viele Nebenbedingungen, wie die Achse< / span > < / span >
< span id = "cb10-125" > < a href = "#cb10-125" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # ' ressource' Eintraege hat. Genau das ist der Punkt.< / span > < / span >
< span id = "cb10-126" > < a href = "#cb10-126" aria-hidden = "true" tabindex = "-1" > < / a > modell.add_constraints((verbrauch < span class = "op" > *< / span > x).< span class = "bu" > sum< / span > (< span class = "st" > " produkt" < / span > ) < span class = "op" > < =< / span > vorrat,< / span >
< span id = "cb10-127" > < a href = "#cb10-127" aria-hidden = "true" tabindex = "-1" > < / a > name< span class = "op" > =< / span > < span class = "st" > " kapazitaet" < / span > )< / span >
< span id = "cb10-128" > < a href = "#cb10-128" aria-hidden = "true" tabindex = "-1" > < / a > modell.add_objective((db < span class = "op" > *< / span > x).< span class = "bu" > sum< / span > (), sense< span class = "op" > =< / span > < span class = "st" > " max" < / span > )< / span >
< span id = "cb10-129" > < a href = "#cb10-129" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-130" > < a href = "#cb10-130" aria-hidden = "true" tabindex = "-1" > < / a > modell.solve(solver_name< span class = "op" > =< / span > < span class = "st" > " highs" < / span > , output_flag< span class = "op" > =< / span > < span class = "va" > False< / span > )< / span >
< span id = "cb10-131" > < a href = "#cb10-131" aria-hidden = "true" tabindex = "-1" > < / a > dauer < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb10-132" > < a href = "#cb10-132" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-133" > < a href = "#cb10-133" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > modell.termination_condition < span class = "op" > !=< / span > < span class = "st" > " optimal" < / span > :< / span >
< span id = "cb10-134" > < a href = "#cb10-134" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > raise< / span > < span class = "pp" > RuntimeError< / span > (< span class = "ss" > f" Linopy meldet Status: < / span > < span class = "sc" > {< / span > modell< span class = "sc" > .< / span > termination_condition< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb10-135" > < a href = "#cb10-135" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-136" > < a href = "#cb10-136" aria-hidden = "true" tabindex = "-1" > < / a > loesung < span class = "op" > =< / span > modell.variables[< span class = "st" > " menge" < / span > ].solution.to_series()< / span >
< span id = "cb10-137" > < a href = "#cb10-137" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > (< span class = "bu" > float< / span > (modell.objective.value),< / span >
< span id = "cb10-138" > < a href = "#cb10-138" aria-hidden = "true" tabindex = "-1" > < / a > [< span class = "bu" > float< / span > (loesung[p]) < span class = "cf" > for< / span > p < span class = "kw" > in< / span > PRODUKTE],< / span >
< span id = "cb10-139" > < a href = "#cb10-139" aria-hidden = "true" tabindex = "-1" > < / a > dauer)< / span >
< span id = "cb10-140" > < a href = "#cb10-140" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-141" > < a href = "#cb10-141" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-142" > < a href = "#cb10-142" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > < span class = "va" > __name__< / span > < span class = "op" > ==< / span > < span class = "st" > " __main__" < / span > :< / span >
< span id = "cb10-143" > < a href = "#cb10-143" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 80< / span > )< / span >
< span id = "cb10-144" > < a href = "#cb10-144" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " MODELLIERUNGSSCHICHTEN FUER GROSSE MODELLE" < / span > )< / span >
< span id = "cb10-145" > < a href = "#cb10-145" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 80< / span > )< / span >
< span id = "cb10-146" > < a href = "#cb10-146" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' Schicht' < / span > < span class = "sc" > :< 14}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Z*' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Standard' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Komfort' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb10-147" > < a href = "#cb10-147" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' Premium' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Zeit' < / span > < span class = "sc" > :> 10}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb10-148" > < a href = "#cb10-148" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 80< / span > )< / span >
< span id = "cb10-149" > < a href = "#cb10-149" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-150" > < a href = "#cb10-150" aria-hidden = "true" tabindex = "-1" > < / a > ergebnisse < span class = "op" > =< / span > []< / span >
< span id = "cb10-151" > < a href = "#cb10-151" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > name, loeser < span class = "kw" > in< / span > [(< span class = "st" > " Pyomo" < / span > , loese_mit_pyomo), (< span class = "st" > " Linopy" < / span > , loese_mit_linopy)]:< / span >
< span id = "cb10-152" > < a href = "#cb10-152" aria-hidden = "true" tabindex = "-1" > < / a > ziel, mengen, dauer < span class = "op" > =< / span > loeser()< / span >
< span id = "cb10-153" > < a href = "#cb10-153" aria-hidden = "true" tabindex = "-1" > < / a > ergebnisse.append(ziel)< / span >
< span id = "cb10-154" > < a href = "#cb10-154" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > name< span class = "sc" > :< 14}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > ziel< span class = "sc" > :> 10.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > mengen[< span class = "dv" > 0< / span > ]< span class = "sc" > :> 10.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > mengen[< span class = "dv" > 1< / span > ]< span class = "sc" > :> 10.2f}< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb10-155" > < a href = "#cb10-155" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > mengen[< span class = "dv" > 2< / span > ]< span class = "sc" > :> 10.2f}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > dauer< span class = "sc" > :> 8.2f}< / span > < span class = "ss" > s" < / span > )< / span >
< span id = "cb10-156" > < a href = "#cb10-156" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb10-157" > < a href = "#cb10-157" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 80< / span > )< / span >
< span id = "cb10-158" > < a href = "#cb10-158" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > ziel < span class = "kw" > in< / span > ergebnisse:< / span >
< span id = "cb10-159" > < a href = "#cb10-159" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > assert< / span > < span class = "bu" > abs< / span > (ziel < span class = "op" > -< / span > ERWARTET) < span class = "op" > < < / span > < span class = "fl" > 1e-6< / span > , < span class = "op" > \< / span > < / span >
< span id = "cb10-160" > < a href = "#cb10-160" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" Abweichung von der Handrechnung: < / span > < span class = "sc" > {< / span > ziel< span class = "sc" > }< / span > < span class = "ss" > statt < / span > < span class = "sc" > {< / span > ERWARTET< span class = "sc" > }< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb10-161" > < a href = "#cb10-161" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" Beide stimmen mit der Handrechnung ueberein (Z* = < / span > < span class = "sc" > {< / span > ERWARTET< span class = "sc" > :.0f}< / span > < span class = "ss" > )." < / span > )< / span >
< span id = "cb10-162" > < a href = "#cb10-162" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > ()< / span >
< span id = "cb10-163" > < a href = "#cb10-163" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Wann welche Schicht?" < / span > )< / span >
< span id = "cb10-164" > < a href = "#cb10-164" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Pyomo -> wenn Modellstruktur und Daten getrennt bleiben sollen," < / span > )< / span >
< span id = "cb10-165" > < a href = "#cb10-165" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " wenn nichtlineare Terme oder MINLP dazukommen koennen," < / span > )< / span >
< span id = "cb10-166" > < a href = "#cb10-166" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " wenn spaeter ein kommerzieller Solver angebunden wird." < / span > )< / span >
< span id = "cb10-167" > < a href = "#cb10-167" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Linopy -> wenn die Daten ohnehin als beschriftete Arrays vorliegen" < / span > )< / span >
< span id = "cb10-168" > < a href = "#cb10-168" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " (Energiesystem-, Netz- und Zeitreihenmodelle) und das" < / span > )< / span >
< span id = "cb10-169" > < a href = "#cb10-169" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Modell zehntausende gleichartige Nebenbedingungen hat." < / span > )< / span >
< span id = "cb10-170" > < a href = "#cb10-170" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 80< / span > )< / span > < / code > < / pre > < / div >
< p > < strong > Erwartete Ausgabe (Zeiten hardwareabhängig):< / strong > < / p >
< pre > < code > ================================================================================
MODELLIERUNGSSCHICHTEN FUER GROSSE MODELLE
================================================================================
Schicht Z* Standard Komfort Premium Zeit
--------------------------------------------------------------------------------
Pyomo 530.00 0.00 12.00 14.00 0.02 s
Linopy 530.00 0.00 12.00 14.00 0.27 s
--------------------------------------------------------------------------------
Beide stimmen mit der Handrechnung ueberein (Z* = 530).
Wann welche Schicht?
Pyomo -> wenn Modellstruktur und Daten getrennt bleiben sollen,
wenn nichtlineare Terme oder MINLP dazukommen koennen,
wenn spaeter ein kommerzieller Solver angebunden wird.
Linopy -> wenn die Daten ohnehin als beschriftete Arrays vorliegen
(Energiesystem-, Netz- und Zeitreihenmodelle) und das
Modell zehntausende gleichartige Nebenbedingungen hat.
================================================================================< / code > < / pre >
< blockquote >
< p > < strong > ⚠️ Lassen Sie sich von den 0,27 s bei Linopy nicht täuschen.< / strong > Bei drei Variablen misst man ausschließlich Startkosten; Linopys Stärke liegt naturgemäß dort, wo es viele gleichartige Nebenbedingungen auf einmal erzeugt. Ein Werkzeug an einem Spielzeugmodell zu bewerten ist einer der häufigsten Benchmark-Fehler — < a href = "praxisfallen.html#kap-praxisfallen" > Kapitel 22< / a > zeigt, wie man es richtig macht.< / p >
< / blockquote >
< hr / >
< h2 id = "sec:oekosystem-vektorisierung" > 3.8 Wo die Zeit wirklich hingeht: vektorisierte Modellgenerierung< / h2 >
< p > Eine der hartnäckigsten Fehlannahmen in Optimierungsprojekten lautet: < em > „Wenn es zu langsam ist, brauchen wir einen besseren Solver.“< / em > Messen Sie erst — oft stimmt das nicht.< / p >
< p > Der Grund ist strukturell. Bevor der Solver auch nur eine Iteration rechnet, muss das Modell < strong > aufgebaut< / strong > werden: Variablen anlegen, Ausdrücke zusammensetzen, Nebenbedingungen an die C++-Schicht übergeben. Dieser Aufbau läuft in < strong > Python< / strong > , der Solver läuft in < strong > C++< / strong > — und zwischen beiden liegen leicht zwei Größenordnungen Geschwindigkeit.< / p >
< blockquote >
< p > < strong > 🎯 Merksatz< / strong > Bei jedem Optimierungsproblem gibt es zwei Laufzeiten: die zum < strong > Aufbauen< / strong > und die zum < strong > Lösen< / strong > . Messen Sie beide getrennt, bevor Sie irgendetwas optimieren. Wer den kleineren Anteil beschleunigt, hat viel Arbeit für wenig Wirkung.< / p >
< / blockquote >
< h3 id = "vier-stufen-an-einem-transportproblem" > Vier Stufen an einem Transportproblem< / h3 >
< p > Wir bauen dasselbe Transportproblem (< span class = "math inline" > m< / span > Werke, < span class = "math inline" > n< / span > Kunden, < span class = "math inline" > m \cdot n< / span > Variablen) auf vier Arten auf:< / p >
< table >
< colgroup >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Stufe< / th >
< th > Wie das Modell entsteht< / th >
< th > Was daran teuer ist< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > < strong > A< / strong > < / td >
< td > OR-Tools, ein < code > Add()< / code > je Nebenbedingung< / td >
< td > Je Aufruf entsteht in Python ein Ausdrucksbaum aus < span class = "math inline" > n< / span > Termen< / td >
< / tr >
< tr class = "even" >
< td > < strong > B< / strong > < / td >
< td > Nebenbedingungsmatrix als COO-Tripel, in Python-Schleifen< / td >
< td > Kein Ausdrucksbaum mehr — aber die Schleife bleibt Python< / td >
< / tr >
< tr class = "odd" >
< td > < strong > C< / strong > < / td >
< td > dieselbe Matrix über Kronecker-Produkte (NumPy/SciPy)< / td >
< td > nichts: eine Handvoll Array-Operationen< / td >
< / tr >
< tr class = "even" >
< td > < strong > D< / strong > < / td >
< td > Daten kommen als lange Tabelle, aufbereitet mit < strong > Polars< / strong > < / td >
< td > nichts: ein Spaltenausdruck statt einer Zeilenschleife< / td >
< / tr >
< / tbody >
< / table >
< p > Stufe D ist der realistische Fall: Kostenmatrizen liegen in der Praxis selten als < span class = "math inline" > m \times n< / span > -Array vor, sondern als lange Tabelle < code > (werk, kunde, kosten)< / code > aus Datenbank oder Data Lake. Polars berechnet daraus die Spaltenindizes der dünnbesetzten Matrix in einem einzigen Ausdruck.< / p >
< div class = "sourceCode" id = "cb12" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb12-1" > < a href = "#cb12-1" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > #!/usr/bin/env python3< / span > < / span >
< span id = "cb12-2" > < a href = "#cb12-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-3" > < a href = "#cb12-3" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Vektorisierte_Modellgenerierung.py< / span > < / span >
< span id = "cb12-4" > < a href = "#cb12-4" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-5" > < a href = "#cb12-5" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Kapitel Oekosystem: Warum der Solver oft gar nicht der Engpass ist.< / span > < / span >
< span id = "cb12-6" > < a href = "#cb12-6" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-7" > < a href = "#cb12-7" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > In realen Projekten geht ein grosser Teil der Rechenzeit nicht ins Loesen,< / span > < / span >
< span id = "cb12-8" > < a href = "#cb12-8" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > sondern ins AUFBAUEN des Modells. Dieses Programm misst das an einem< / span > < / span >
< span id = "cb12-9" > < a href = "#cb12-9" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Transportproblem wachsender Groesse in vier Stufen:< / span > < / span >
< span id = "cb12-10" > < a href = "#cb12-10" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-11" > < a href = "#cb12-11" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > A Modellierungsschicht, Nebenbedingung fuer Nebenbedingung (OR-Tools)< / span > < / span >
< span id = "cb12-12" > < a href = "#cb12-12" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > B Matrix direkt, aber mit Python-Schleifen ueber die Eintraege (COO)< / span > < / span >
< span id = "cb12-13" > < a href = "#cb12-13" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > C Matrix vektorisiert ueber Kronecker-Produkte (NumPy/SciPy)< / span > < / span >
< span id = "cb12-14" > < a href = "#cb12-14" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > D Daten kommen als lange Tabelle, aufbereitet mit Polars< / span > < / span >
< span id = "cb12-15" > < a href = "#cb12-15" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-16" > < a href = "#cb12-16" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Alle Varianten loesen dasselbe Problem und muessen denselben Zielwert< / span > < / span >
< span id = "cb12-17" > < a href = "#cb12-17" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > liefern - das wird am Ende geprueft.< / span > < / span >
< span id = "cb12-18" > < a href = "#cb12-18" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-19" > < a href = "#cb12-19" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Benoetigt: numpy, scipy, ortools; Variante D zusaetzlich polars (optional).< / span > < / span >
< span id = "cb12-20" > < a href = "#cb12-20" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-21" > < a href = "#cb12-21" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-22" > < a href = "#cb12-22" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > __future__ < span class = "im" > import< / span > annotations< / span >
< span id = "cb12-23" > < a href = "#cb12-23" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-24" > < a href = "#cb12-24" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > importlib.util< / span >
< span id = "cb12-25" > < a href = "#cb12-25" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > time< / span >
< span id = "cb12-26" > < a href = "#cb12-26" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-27" > < a href = "#cb12-27" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > numpy < span class = "im" > as< / span > np< / span >
< span id = "cb12-28" > < a href = "#cb12-28" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > scipy.sparse < span class = "im" > as< / span > sp< / span >
< span id = "cb12-29" > < a href = "#cb12-29" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > ortools.linear_solver < span class = "im" > import< / span > pywraplp< / span >
< span id = "cb12-30" > < a href = "#cb12-30" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > scipy.optimize < span class = "im" > import< / span > linprog< / span >
< span id = "cb12-31" > < a href = "#cb12-31" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-32" > < a href = "#cb12-32" aria-hidden = "true" tabindex = "-1" > < / a > HAT_POLARS < span class = "op" > =< / span > importlib.util.find_spec(< span class = "st" > " polars" < / span > ) < span class = "kw" > is< / span > < span class = "kw" > not< / span > < span class = "va" > None< / span > < / span >
< span id = "cb12-33" > < a href = "#cb12-33" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-34" > < a href = "#cb12-34" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-35" > < a href = "#cb12-35" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > erzeuge_daten(m: < span class = "bu" > int< / span > , n: < span class = "bu" > int< / span > , saat: < span class = "bu" > int< / span > < span class = "op" > =< / span > < span class = "dv" > 3< / span > < / span >
< span id = "cb12-36" > < a href = "#cb12-36" aria-hidden = "true" tabindex = "-1" > < / a > ) < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [np.ndarray, np.ndarray, np.ndarray]:< / span >
< span id = "cb12-37" > < a href = "#cb12-37" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Transportproblem: m Werke, n Kunden.< / span > < / span >
< span id = "cb12-38" > < a href = "#cb12-38" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-39" > < a href = "#cb12-39" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Liefert (kosten[m, n], angebot[m], bedarf[n]). Das Gesamtangebot liegt< / span > < / span >
< span id = "cb12-40" > < a href = "#cb12-40" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > 20 % ueber dem Gesamtbedarf, damit das Modell sicher loesbar ist.< / span > < / span >
< span id = "cb12-41" > < a href = "#cb12-41" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-42" > < a href = "#cb12-42" aria-hidden = "true" tabindex = "-1" > < / a > rng < span class = "op" > =< / span > np.random.default_rng(saat)< / span >
< span id = "cb12-43" > < a href = "#cb12-43" aria-hidden = "true" tabindex = "-1" > < / a > kosten < span class = "op" > =< / span > rng.uniform(< span class = "fl" > 1.0< / span > , < span class = "fl" > 20.0< / span > , size< span class = "op" > =< / span > (m, n))< / span >
< span id = "cb12-44" > < a href = "#cb12-44" aria-hidden = "true" tabindex = "-1" > < / a > bedarf < span class = "op" > =< / span > rng.uniform(< span class = "fl" > 10.0< / span > , < span class = "fl" > 50.0< / span > , size< span class = "op" > =< / span > n)< / span >
< span id = "cb12-45" > < a href = "#cb12-45" aria-hidden = "true" tabindex = "-1" > < / a > angebot < span class = "op" > =< / span > np.full(m, < span class = "fl" > 1.2< / span > < span class = "op" > *< / span > bedarf.< span class = "bu" > sum< / span > () < span class = "op" > /< / span > m)< / span >
< span id = "cb12-46" > < a href = "#cb12-46" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > kosten, angebot, bedarf< / span >
< span id = "cb12-47" > < a href = "#cb12-47" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-48" > < a href = "#cb12-48" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-49" > < a href = "#cb12-49" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # --- Variante A: Modellierungsschicht, Bedingung fuer Bedingung -------------< / span > < / span >
< span id = "cb12-50" > < a href = "#cb12-50" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-51" > < a href = "#cb12-51" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > loese_mit_modellierungsschicht(kosten: np.ndarray, angebot: np.ndarray,< / span >
< span id = "cb12-52" > < a href = "#cb12-52" aria-hidden = "true" tabindex = "-1" > < / a > bedarf: np.ndarray) < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > float< / span > , < span class = "bu" > float< / span > ]:< / span >
< span id = "cb12-53" > < a href = "#cb12-53" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " So schreibt man ein Transportproblem zuerst hin - gut lesbar, nah an< / span > < / span >
< span id = "cb12-54" > < a href = "#cb12-54" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > der mathematischen Formulierung, jede Nebenbedingung ein eigener Aufruf.< / span > < / span >
< span id = "cb12-55" > < a href = "#cb12-55" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-56" > < a href = "#cb12-56" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Jedes `s.Add(sum(...))` baut in Python einen Ausdrucksbaum aus m bzw. n< / span > < / span >
< span id = "cb12-57" > < a href = "#cb12-57" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Termen auf und uebergibt ihn einzeln an die C++-Schicht. Das ist der< / span > < / span >
< span id = "cb12-58" > < a href = "#cb12-58" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Preis der Bequemlichkeit - und er waechst linear mit der Modellgroesse.< / span > < / span >
< span id = "cb12-59" > < a href = "#cb12-59" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-60" > < a href = "#cb12-60" aria-hidden = "true" tabindex = "-1" > < / a > m, n < span class = "op" > =< / span > kosten.shape< / span >
< span id = "cb12-61" > < a href = "#cb12-61" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-62" > < a href = "#cb12-62" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb12-63" > < a href = "#cb12-63" aria-hidden = "true" tabindex = "-1" > < / a > s < span class = "op" > =< / span > pywraplp.Solver.CreateSolver(< span class = "st" > " GLOP" < / span > )< / span >
< span id = "cb12-64" > < a href = "#cb12-64" aria-hidden = "true" tabindex = "-1" > < / a > x < span class = "op" > =< / span > [[s.NumVar(< span class = "dv" > 0< / span > , s.infinity(), < span class = "ss" > f" x_< / span > < span class = "sc" > {< / span > i< span class = "sc" > }< / span > < span class = "ss" > _< / span > < span class = "sc" > {< / span > j< span class = "sc" > }< / span > < span class = "ss" > " < / span > ) < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n)]< / span >
< span id = "cb12-65" > < a href = "#cb12-65" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m)]< / span >
< span id = "cb12-66" > < a href = "#cb12-66" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m):< / span >
< span id = "cb12-67" > < a href = "#cb12-67" aria-hidden = "true" tabindex = "-1" > < / a > s.Add(< span class = "bu" > sum< / span > (x[i][j] < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n)) < span class = "op" > < =< / span > angebot[i])< / span >
< span id = "cb12-68" > < a href = "#cb12-68" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n):< / span >
< span id = "cb12-69" > < a href = "#cb12-69" aria-hidden = "true" tabindex = "-1" > < / a > s.Add(< span class = "bu" > sum< / span > (x[i][j] < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m)) < span class = "op" > > =< / span > bedarf[j])< / span >
< span id = "cb12-70" > < a href = "#cb12-70" aria-hidden = "true" tabindex = "-1" > < / a > s.Minimize(< span class = "bu" > sum< / span > (kosten[i][j] < span class = "op" > *< / span > x[i][j] < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m) < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n)))< / span >
< span id = "cb12-71" > < a href = "#cb12-71" aria-hidden = "true" tabindex = "-1" > < / a > t_aufbau < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb12-72" > < a href = "#cb12-72" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-73" > < a href = "#cb12-73" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb12-74" > < a href = "#cb12-74" aria-hidden = "true" tabindex = "-1" > < / a > status < span class = "op" > =< / span > s.Solve()< / span >
< span id = "cb12-75" > < a href = "#cb12-75" aria-hidden = "true" tabindex = "-1" > < / a > t_loesen < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb12-76" > < a href = "#cb12-76" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > status < span class = "op" > !=< / span > pywraplp.Solver.OPTIMAL:< / span >
< span id = "cb12-77" > < a href = "#cb12-77" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > raise< / span > < span class = "pp" > RuntimeError< / span > (< span class = "ss" > f" Solver-Status: < / span > < span class = "sc" > {< / span > status< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb12-78" > < a href = "#cb12-78" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > t_aufbau, t_loesen, s.Objective().Value()< / span >
< span id = "cb12-79" > < a href = "#cb12-79" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-80" > < a href = "#cb12-80" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-81" > < a href = "#cb12-81" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # --- Varianten B bis D: Matrix selbst bauen, dann SciPy/HiGHS ---------------< / span > < / span >
< span id = "cb12-82" > < a href = "#cb12-82" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-83" > < a href = "#cb12-83" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > baue_mit_schleifen(kosten: np.ndarray, angebot: np.ndarray,< / span >
< span id = "cb12-84" > < a href = "#cb12-84" aria-hidden = "true" tabindex = "-1" > < / a > bedarf: np.ndarray):< / span >
< span id = "cb12-85" > < a href = "#cb12-85" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Die Nebenbedingungsmatrix als COO-Tripel (Zeile, Spalte, Wert), erzeugt< / span > < / span >
< span id = "cb12-86" > < a href = "#cb12-86" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > in verschachtelten Python-Schleifen.< / span > < / span >
< span id = "cb12-87" > < a href = "#cb12-87" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-88" > < a href = "#cb12-88" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Schon deutlich naeher am Blech als Variante A - es entsteht kein< / span > < / span >
< span id = "cb12-89" > < a href = "#cb12-89" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Ausdrucksbaum mehr. Die Schleife selbst bleibt aber Python.< / span > < / span >
< span id = "cb12-90" > < a href = "#cb12-90" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-91" > < a href = "#cb12-91" aria-hidden = "true" tabindex = "-1" > < / a > m, n < span class = "op" > =< / span > kosten.shape< / span >
< span id = "cb12-92" > < a href = "#cb12-92" aria-hidden = "true" tabindex = "-1" > < / a > zeilen: < span class = "bu" > list< / span > [< span class = "bu" > int< / span > ] < span class = "op" > =< / span > []< / span >
< span id = "cb12-93" > < a href = "#cb12-93" aria-hidden = "true" tabindex = "-1" > < / a > spalten: < span class = "bu" > list< / span > [< span class = "bu" > int< / span > ] < span class = "op" > =< / span > []< / span >
< span id = "cb12-94" > < a href = "#cb12-94" aria-hidden = "true" tabindex = "-1" > < / a > werte: < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ] < span class = "op" > =< / span > []< / span >
< span id = "cb12-95" > < a href = "#cb12-95" aria-hidden = "true" tabindex = "-1" > < / a > rechte_seite: < span class = "bu" > list< / span > [< span class = "bu" > float< / span > ] < span class = "op" > =< / span > []< / span >
< span id = "cb12-96" > < a href = "#cb12-96" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-97" > < a href = "#cb12-97" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m): < span class = "co" > # Angebot je Werk< / span > < / span >
< span id = "cb12-98" > < a href = "#cb12-98" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n):< / span >
< span id = "cb12-99" > < a href = "#cb12-99" aria-hidden = "true" tabindex = "-1" > < / a > zeilen.append(i)< / span >
< span id = "cb12-100" > < a href = "#cb12-100" aria-hidden = "true" tabindex = "-1" > < / a > spalten.append(i < span class = "op" > *< / span > n < span class = "op" > +< / span > j)< / span >
< span id = "cb12-101" > < a href = "#cb12-101" aria-hidden = "true" tabindex = "-1" > < / a > werte.append(< span class = "fl" > 1.0< / span > )< / span >
< span id = "cb12-102" > < a href = "#cb12-102" aria-hidden = "true" tabindex = "-1" > < / a > rechte_seite.append(< span class = "bu" > float< / span > (angebot[i]))< / span >
< span id = "cb12-103" > < a href = "#cb12-103" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-104" > < a href = "#cb12-104" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > j < span class = "kw" > in< / span > < span class = "bu" > range< / span > (n): < span class = "co" > # Bedarf je Kunde, als -x < = -bedarf< / span > < / span >
< span id = "cb12-105" > < a href = "#cb12-105" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > i < span class = "kw" > in< / span > < span class = "bu" > range< / span > (m):< / span >
< span id = "cb12-106" > < a href = "#cb12-106" aria-hidden = "true" tabindex = "-1" > < / a > zeilen.append(m < span class = "op" > +< / span > j)< / span >
< span id = "cb12-107" > < a href = "#cb12-107" aria-hidden = "true" tabindex = "-1" > < / a > spalten.append(i < span class = "op" > *< / span > n < span class = "op" > +< / span > j)< / span >
< span id = "cb12-108" > < a href = "#cb12-108" aria-hidden = "true" tabindex = "-1" > < / a > werte.append(< span class = "op" > -< / span > < span class = "fl" > 1.0< / span > )< / span >
< span id = "cb12-109" > < a href = "#cb12-109" aria-hidden = "true" tabindex = "-1" > < / a > rechte_seite.append(< span class = "op" > -< / span > < span class = "bu" > float< / span > (bedarf[j]))< / span >
< span id = "cb12-110" > < a href = "#cb12-110" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-111" > < a href = "#cb12-111" aria-hidden = "true" tabindex = "-1" > < / a > A_ub < span class = "op" > =< / span > sp.csr_matrix((werte, (zeilen, spalten)), shape< span class = "op" > =< / span > (m < span class = "op" > +< / span > n, m < span class = "op" > *< / span > n))< / span >
< span id = "cb12-112" > < a href = "#cb12-112" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > A_ub, np.array(rechte_seite), kosten.ravel()< / span >
< span id = "cb12-113" > < a href = "#cb12-113" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-114" > < a href = "#cb12-114" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-115" > < a href = "#cb12-115" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > baue_vektorisiert(kosten: np.ndarray, angebot: np.ndarray,< / span >
< span id = "cb12-116" > < a href = "#cb12-116" aria-hidden = "true" tabindex = "-1" > < / a > bedarf: np.ndarray):< / span >
< span id = "cb12-117" > < a href = "#cb12-117" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Dieselbe Matrix ohne eine einzige Schleife - ueber Kronecker-Produkte.< / span > < / span >
< span id = "cb12-118" > < a href = "#cb12-118" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-119" > < a href = "#cb12-119" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Die Angebotsmatrix ist kron(I_m, 1_n^T): je Werk eine Zeile mit Einsen< / span > < / span >
< span id = "cb12-120" > < a href = "#cb12-120" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > an genau den n Spalten dieses Werks. Die Bedarfsmatrix ist< / span > < / span >
< span id = "cb12-121" > < a href = "#cb12-121" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > kron(1_m^T, I_n). Beide entstehen in je einem Aufruf und sind sofort< / span > < / span >
< span id = "cb12-122" > < a href = "#cb12-122" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > duennbesetzt.< / span > < / span >
< span id = "cb12-123" > < a href = "#cb12-123" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-124" > < a href = "#cb12-124" aria-hidden = "true" tabindex = "-1" > < / a > m, n < span class = "op" > =< / span > kosten.shape< / span >
< span id = "cb12-125" > < a href = "#cb12-125" aria-hidden = "true" tabindex = "-1" > < / a > angebots_matrix < span class = "op" > =< / span > sp.kron(sp.identity(m, < span class = "bu" > format< / span > < span class = "op" > =< / span > < span class = "st" > " csr" < / span > ), np.ones((< span class = "dv" > 1< / span > , n)))< / span >
< span id = "cb12-126" > < a href = "#cb12-126" aria-hidden = "true" tabindex = "-1" > < / a > bedarfs_matrix < span class = "op" > =< / span > sp.kron(np.ones((< span class = "dv" > 1< / span > , m)), sp.identity(n, < span class = "bu" > format< / span > < span class = "op" > =< / span > < span class = "st" > " csr" < / span > ))< / span >
< span id = "cb12-127" > < a href = "#cb12-127" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-128" > < a href = "#cb12-128" aria-hidden = "true" tabindex = "-1" > < / a > A_ub < span class = "op" > =< / span > sp.vstack([angebots_matrix, < span class = "op" > -< / span > bedarfs_matrix], < span class = "bu" > format< / span > < span class = "op" > =< / span > < span class = "st" > " csr" < / span > )< / span >
< span id = "cb12-129" > < a href = "#cb12-129" aria-hidden = "true" tabindex = "-1" > < / a > b_ub < span class = "op" > =< / span > np.concatenate([angebot, < span class = "op" > -< / span > bedarf])< / span >
< span id = "cb12-130" > < a href = "#cb12-130" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > A_ub, b_ub, kosten.ravel()< / span >
< span id = "cb12-131" > < a href = "#cb12-131" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-132" > < a href = "#cb12-132" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-133" > < a href = "#cb12-133" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > baue_mit_polars(kosten: np.ndarray, angebot: np.ndarray, bedarf: np.ndarray):< / span >
< span id = "cb12-134" > < a href = "#cb12-134" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Der realistische Fall: Die Kosten kommen als LANGE Tabelle< / span > < / span >
< span id = "cb12-135" > < a href = "#cb12-135" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > (werk, kunde, kosten) aus Datenbank, Data Lake oder CSV-Datei.< / span > < / span >
< span id = "cb12-136" > < a href = "#cb12-136" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-137" > < a href = "#cb12-137" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Polars berechnet den Spaltenindex jeder Variablen in einem einzigen< / span > < / span >
< span id = "cb12-138" > < a href = "#cb12-138" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Spaltenausdruck - ohne Python-Schleife ueber die Zeilen. Genau so baut man< / span > < / span >
< span id = "cb12-139" > < a href = "#cb12-139" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > Modelle aus Millionen Tabellenzeilen.< / span > < / span >
< span id = "cb12-140" > < a href = "#cb12-140" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " < / span > < / span >
< span id = "cb12-141" > < a href = "#cb12-141" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > polars < span class = "im" > as< / span > pl< / span >
< span id = "cb12-142" > < a href = "#cb12-142" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-143" > < a href = "#cb12-143" aria-hidden = "true" tabindex = "-1" > < / a > m, n < span class = "op" > =< / span > kosten.shape< / span >
< span id = "cb12-144" > < a href = "#cb12-144" aria-hidden = "true" tabindex = "-1" > < / a > tabelle < span class = "op" > =< / span > pl.DataFrame({< / span >
< span id = "cb12-145" > < a href = "#cb12-145" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " werk" < / span > : np.repeat(np.arange(m), n),< / span >
< span id = "cb12-146" > < a href = "#cb12-146" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " kunde" < / span > : np.tile(np.arange(n), m),< / span >
< span id = "cb12-147" > < a href = "#cb12-147" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "st" > " kosten" < / span > : kosten.ravel(),< / span >
< span id = "cb12-148" > < a href = "#cb12-148" aria-hidden = "true" tabindex = "-1" > < / a > }).with_columns(< / span >
< span id = "cb12-149" > < a href = "#cb12-149" aria-hidden = "true" tabindex = "-1" > < / a > (pl.col(< span class = "st" > " werk" < / span > ) < span class = "op" > *< / span > n < span class = "op" > +< / span > pl.col(< span class = "st" > " kunde" < / span > )).alias(< span class = "st" > " var_index" < / span > )< / span >
< span id = "cb12-150" > < a href = "#cb12-150" aria-hidden = "true" tabindex = "-1" > < / a > )< / span >
< span id = "cb12-151" > < a href = "#cb12-151" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-152" > < a href = "#cb12-152" aria-hidden = "true" tabindex = "-1" > < / a > var_index < span class = "op" > =< / span > tabelle[< span class = "st" > " var_index" < / span > ].to_numpy()< / span >
< span id = "cb12-153" > < a href = "#cb12-153" aria-hidden = "true" tabindex = "-1" > < / a > werk < span class = "op" > =< / span > tabelle[< span class = "st" > " werk" < / span > ].to_numpy()< / span >
< span id = "cb12-154" > < a href = "#cb12-154" aria-hidden = "true" tabindex = "-1" > < / a > kunde < span class = "op" > =< / span > tabelle[< span class = "st" > " kunde" < / span > ].to_numpy()< / span >
< span id = "cb12-155" > < a href = "#cb12-155" aria-hidden = "true" tabindex = "-1" > < / a > eins < span class = "op" > =< / span > np.ones(var_index.size)< / span >
< span id = "cb12-156" > < a href = "#cb12-156" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-157" > < a href = "#cb12-157" aria-hidden = "true" tabindex = "-1" > < / a > angebots_matrix < span class = "op" > =< / span > sp.csr_matrix((eins, (werk, var_index)), shape< span class = "op" > =< / span > (m, m < span class = "op" > *< / span > n))< / span >
< span id = "cb12-158" > < a href = "#cb12-158" aria-hidden = "true" tabindex = "-1" > < / a > bedarfs_matrix < span class = "op" > =< / span > sp.csr_matrix((eins, (kunde, var_index)), shape< span class = "op" > =< / span > (n, m < span class = "op" > *< / span > n))< / span >
< span id = "cb12-159" > < a href = "#cb12-159" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-160" > < a href = "#cb12-160" aria-hidden = "true" tabindex = "-1" > < / a > A_ub < span class = "op" > =< / span > sp.vstack([angebots_matrix, < span class = "op" > -< / span > bedarfs_matrix], < span class = "bu" > format< / span > < span class = "op" > =< / span > < span class = "st" > " csr" < / span > )< / span >
< span id = "cb12-161" > < a href = "#cb12-161" aria-hidden = "true" tabindex = "-1" > < / a > b_ub < span class = "op" > =< / span > np.concatenate([angebot, < span class = "op" > -< / span > bedarf])< / span >
< span id = "cb12-162" > < a href = "#cb12-162" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > A_ub, b_ub, tabelle[< span class = "st" > " kosten" < / span > ].to_numpy()< / span >
< span id = "cb12-163" > < a href = "#cb12-163" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-164" > < a href = "#cb12-164" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-165" > < a href = "#cb12-165" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "kw" > def< / span > messe_matrixvariante(bauer, kosten, angebot, bedarf< / span >
< span id = "cb12-166" > < a href = "#cb12-166" aria-hidden = "true" tabindex = "-1" > < / a > ) < span class = "op" > -> < / span > < span class = "bu" > tuple< / span > [< span class = "bu" > float< / span > , < span class = "bu" > float< / span > , < span class = "bu" > float< / span > ]:< / span >
< span id = "cb12-167" > < a href = "#cb12-167" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > " " " Liefert (Aufbauzeit, Loesezeit, Zielwert) fuer die Varianten B bis D." " " < / span > < / span >
< span id = "cb12-168" > < a href = "#cb12-168" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb12-169" > < a href = "#cb12-169" aria-hidden = "true" tabindex = "-1" > < / a > A_ub, b_ub, c < span class = "op" > =< / span > bauer(kosten, angebot, bedarf)< / span >
< span id = "cb12-170" > < a href = "#cb12-170" aria-hidden = "true" tabindex = "-1" > < / a > t_aufbau < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb12-171" > < a href = "#cb12-171" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-172" > < a href = "#cb12-172" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb12-173" > < a href = "#cb12-173" aria-hidden = "true" tabindex = "-1" > < / a > ergebnis < span class = "op" > =< / span > linprog(c, A_ub< span class = "op" > =< / span > A_ub, b_ub< span class = "op" > =< / span > b_ub, bounds< span class = "op" > =< / span > (< span class = "dv" > 0< / span > , < span class = "va" > None< / span > ), method< span class = "op" > =< / span > < span class = "st" > " highs" < / span > )< / span >
< span id = "cb12-174" > < a href = "#cb12-174" aria-hidden = "true" tabindex = "-1" > < / a > t_loesen < span class = "op" > =< / span > time.perf_counter() < span class = "op" > -< / span > t0< / span >
< span id = "cb12-175" > < a href = "#cb12-175" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-176" > < a href = "#cb12-176" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > < span class = "kw" > not< / span > ergebnis.success:< / span >
< span id = "cb12-177" > < a href = "#cb12-177" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > raise< / span > < span class = "pp" > RuntimeError< / span > (< span class = "ss" > f" Solver-Status: < / span > < span class = "sc" > {< / span > ergebnis< span class = "sc" > .< / span > message< span class = "sc" > }< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb12-178" > < a href = "#cb12-178" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > return< / span > t_aufbau, t_loesen, < span class = "bu" > float< / span > (ergebnis.fun)< / span >
< span id = "cb12-179" > < a href = "#cb12-179" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-180" > < a href = "#cb12-180" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-181" > < a href = "#cb12-181" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > < span class = "va" > __name__< / span > < span class = "op" > ==< / span > < span class = "st" > " __main__" < / span > :< / span >
< span id = "cb12-182" > < a href = "#cb12-182" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 88< / span > )< / span >
< span id = "cb12-183" > < a href = "#cb12-183" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " MODELLAUFBAU: WO DIE ZEIT WIRKLICH HINGEHT" < / span > )< / span >
< span id = "cb12-184" > < a href = "#cb12-184" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 88< / span > )< / span >
< span id = "cb12-185" > < a href = "#cb12-185" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Transportproblem mit m Werken und n Kunden -> m*n Variablen.< / span > < span class = "ch" > \n< / span > < span class = "st" > " < / span > )< / span >
< span id = "cb12-186" > < a href = "#cb12-186" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-187" > < a href = "#cb12-187" aria-hidden = "true" tabindex = "-1" > < / a > varianten: < span class = "bu" > list< / span > [< span class = "bu" > tuple< / span > [< span class = "bu" > str< / span > , < span class = "bu" > object< / span > ]] < span class = "op" > =< / span > [< / span >
< span id = "cb12-188" > < a href = "#cb12-188" aria-hidden = "true" tabindex = "-1" > < / a > (< span class = "st" > " A: OR-Tools, Add() je NB" < / span > , < span class = "va" > None< / span > ), < span class = "co" > # Sonderfall, eigener Messpfad< / span > < / span >
< span id = "cb12-189" > < a href = "#cb12-189" aria-hidden = "true" tabindex = "-1" > < / a > (< span class = "st" > " B: COO in Schleifen" < / span > , baue_mit_schleifen),< / span >
< span id = "cb12-190" > < a href = "#cb12-190" aria-hidden = "true" tabindex = "-1" > < / a > (< span class = "st" > " C: NumPy vektorisiert" < / span > , baue_vektorisiert),< / span >
< span id = "cb12-191" > < a href = "#cb12-191" aria-hidden = "true" tabindex = "-1" > < / a > ]< / span >
< span id = "cb12-192" > < a href = "#cb12-192" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > HAT_POLARS:< / span >
< span id = "cb12-193" > < a href = "#cb12-193" aria-hidden = "true" tabindex = "-1" > < / a > varianten.append((< span class = "st" > " D: Polars-Tabelle" < / span > , baue_mit_polars))< / span >
< span id = "cb12-194" > < a href = "#cb12-194" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > else< / span > :< / span >
< span id = "cb12-195" > < a href = "#cb12-195" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Hinweis: polars nicht installiert - Variante D wird uebersprungen.< / span > < span class = "ch" > \n< / span > < span class = "st" > " < / span > )< / span >
< span id = "cb12-196" > < a href = "#cb12-196" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-197" > < a href = "#cb12-197" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Aufwaermlauf: Der erste Aufruf bezahlt Importe und einmalige< / span > < / span >
< span id = "cb12-198" > < a href = "#cb12-198" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Initialisierungen. Wer den mitmisst, vergleicht Startkosten statt< / span > < / span >
< span id = "cb12-199" > < a href = "#cb12-199" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Rechenarbeit - ein klassischer Benchmark-Fehler.< / span > < / span >
< span id = "cb12-200" > < a href = "#cb12-200" aria-hidden = "true" tabindex = "-1" > < / a > aufwaerm < span class = "op" > =< / span > erzeuge_daten(< span class = "dv" > 10< / span > , < span class = "dv" > 10< / span > )< / span >
< span id = "cb12-201" > < a href = "#cb12-201" aria-hidden = "true" tabindex = "-1" > < / a > loese_mit_modellierungsschicht(< span class = "op" > *< / span > aufwaerm)< / span >
< span id = "cb12-202" > < a href = "#cb12-202" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > _, bauer < span class = "kw" > in< / span > varianten[< span class = "dv" > 1< / span > :]:< / span >
< span id = "cb12-203" > < a href = "#cb12-203" aria-hidden = "true" tabindex = "-1" > < / a > bauer(< span class = "op" > *< / span > aufwaerm)< / span >
< span id = "cb12-204" > < a href = "#cb12-204" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-205" > < a href = "#cb12-205" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' Groesse' < / span > < span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Variante' < / span > < span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Aufbau' < / span > < span class = "sc" > :> 9}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' Loesen' < / span > < span class = "sc" > :> 9}< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb12-206" > < a href = "#cb12-206" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' Aufbauanteil' < / span > < span class = "sc" > :> 13}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb12-207" > < a href = "#cb12-207" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 88< / span > )< / span >
< span id = "cb12-208" > < a href = "#cb12-208" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-209" > < a href = "#cb12-209" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > m, n < span class = "kw" > in< / span > [(< span class = "dv" > 40< / span > , < span class = "dv" > 40< / span > ), (< span class = "dv" > 120< / span > , < span class = "dv" > 120< / span > ), (< span class = "dv" > 250< / span > , < span class = "dv" > 250< / span > )]:< / span >
< span id = "cb12-210" > < a href = "#cb12-210" aria-hidden = "true" tabindex = "-1" > < / a > kosten, angebot, bedarf < span class = "op" > =< / span > erzeuge_daten(m, n)< / span >
< span id = "cb12-211" > < a href = "#cb12-211" aria-hidden = "true" tabindex = "-1" > < / a > zielwerte < span class = "op" > =< / span > []< / span >
< span id = "cb12-212" > < a href = "#cb12-212" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > for< / span > nummer, (name, bauer) < span class = "kw" > in< / span > < span class = "bu" > enumerate< / span > (varianten):< / span >
< span id = "cb12-213" > < a href = "#cb12-213" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > if< / span > bauer < span class = "kw" > is< / span > < span class = "va" > None< / span > :< / span >
< span id = "cb12-214" > < a href = "#cb12-214" aria-hidden = "true" tabindex = "-1" > < / a > t_aufbau, t_loesen, ziel < span class = "op" > =< / span > loese_mit_modellierungsschicht(< / span >
< span id = "cb12-215" > < a href = "#cb12-215" aria-hidden = "true" tabindex = "-1" > < / a > kosten, angebot, bedarf)< / span >
< span id = "cb12-216" > < a href = "#cb12-216" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > else< / span > :< / span >
< span id = "cb12-217" > < a href = "#cb12-217" aria-hidden = "true" tabindex = "-1" > < / a > t_aufbau, t_loesen, ziel < span class = "op" > =< / span > messe_matrixvariante(< / span >
< span id = "cb12-218" > < a href = "#cb12-218" aria-hidden = "true" tabindex = "-1" > < / a > bauer, kosten, angebot, bedarf)< / span >
< span id = "cb12-219" > < a href = "#cb12-219" aria-hidden = "true" tabindex = "-1" > < / a > zielwerte.append(ziel)< / span >
< span id = "cb12-220" > < a href = "#cb12-220" aria-hidden = "true" tabindex = "-1" > < / a > anteil < span class = "op" > =< / span > < span class = "fl" > 100.0< / span > < span class = "op" > *< / span > t_aufbau < span class = "op" > /< / span > (t_aufbau < span class = "op" > +< / span > t_loesen)< / span >
< span id = "cb12-221" > < a href = "#cb12-221" aria-hidden = "true" tabindex = "-1" > < / a > groesse < span class = "op" > =< / span > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > m< span class = "sc" > }< / span > < span class = "ss" > x< / span > < span class = "sc" > {< / span > n< span class = "sc" > }< / span > < span class = "ss" > = < / span > < span class = "sc" > {< / span > m< span class = "op" > *< / span > n< span class = "sc" > :,}< / span > < span class = "ss" > Variablen" < / span > < span class = "cf" > if< / span > nummer < span class = "op" > ==< / span > < span class = "dv" > 0< / span > < span class = "cf" > else< / span > < span class = "st" > " " < / span > < / span >
< span id = "cb12-222" > < a href = "#cb12-222" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > groesse< span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > name< span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > t_aufbau< span class = "sc" > :> 8.3f}< / span > < span class = "ss" > s < / span > < span class = "sc" > {< / span > t_loesen< span class = "sc" > :> 8.3f}< / span > < span class = "ss" > s " < / span > < / span >
< span id = "cb12-223" > < a href = "#cb12-223" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" < / span > < span class = "sc" > {< / span > anteil< span class = "sc" > :> 12.0f}< / span > < span class = "ss" > %" < / span > )< / span >
< span id = "cb12-224" > < a href = "#cb12-224" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-225" > < a href = "#cb12-225" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "co" > # Alle Varianten muessen dasselbe Problem beschreiben.< / span > < / span >
< span id = "cb12-226" > < a href = "#cb12-226" aria-hidden = "true" tabindex = "-1" > < / a > spanne < span class = "op" > =< / span > < span class = "bu" > max< / span > (zielwerte) < span class = "op" > -< / span > < span class = "bu" > min< / span > (zielwerte)< / span >
< span id = "cb12-227" > < a href = "#cb12-227" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "cf" > assert< / span > spanne < span class = "op" > < < / span > < span class = "fl" > 1e-6< / span > < span class = "op" > *< / span > < span class = "bu" > max< / span > (< span class = "bu" > abs< / span > (z) < span class = "cf" > for< / span > z < span class = "kw" > in< / span > zielwerte), < span class = "op" > \< / span > < / span >
< span id = "cb12-228" > < a href = "#cb12-228" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" Varianten widersprechen sich: < / span > < span class = "sc" > {< / span > zielwerte< span class = "sc" > }< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb12-229" > < a href = "#cb12-229" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "ss" > f" < / span > < span class = "sc" > {< / span > < span class = "st" > ' ' < / span > < span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > < span class = "st" > ' -> Zielwert (alle gleich)' < / span > < span class = "sc" > :< 26}< / span > < span class = "ss" > < / span > < span class = "sc" > {< / span > zielwerte[< span class = "dv" > 0< / span > ]< span class = "sc" > :> 9.2f}< / span > < span class = "ss" > " < / span > < / span >
< span id = "cb12-230" > < a href = "#cb12-230" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "ss" > f" Spanne < / span > < span class = "sc" > {< / span > spanne< span class = "sc" > :.1e}< / span > < span class = "ss" > " < / span > )< / span >
< span id = "cb12-231" > < a href = "#cb12-231" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " -" < / span > < span class = "op" > *< / span > < span class = "dv" > 88< / span > )< / span >
< span id = "cb12-232" > < a href = "#cb12-232" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb12-233" > < a href = "#cb12-233" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " < / span > < span class = "ch" > \n< / span > < span class = "st" > Zwei Lehren aus der Tabelle:" < / span > )< / span >
< span id = "cb12-234" > < a href = "#cb12-234" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " 1. Bei Variante A geht mehr Zeit in den AUFBAU als ins Loesen. Wer hier" < / span > )< / span >
< span id = "cb12-235" > < a href = "#cb12-235" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " einen schnelleren Solver kauft, beschleunigt den kleineren Teil." < / span > )< / span >
< span id = "cb12-236" > < a href = "#cb12-236" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " 2. Zwischen B und C liegt keine andere Mathematik, nur eine andere" < / span > )< / span >
< span id = "cb12-237" > < a href = "#cb12-237" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " Schreibweise derselben Matrix - Schleife gegen Kronecker-Produkt." < / span > )< / span >
< span id = "cb12-238" > < a href = "#cb12-238" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " < / span > < span class = "ch" > \n< / span > < span class = "st" > Die Lesbarkeit von Variante A ist trotzdem viel wert: Fangen Sie dort an," < / span > )< / span >
< span id = "cb12-239" > < a href = "#cb12-239" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " und vektorisieren Sie erst, wenn die Messung es verlangt." < / span > )< / span >
< span id = "cb12-240" > < a href = "#cb12-240" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " =" < / span > < span class = "op" > *< / span > < span class = "dv" > 88< / span > )< / span > < / code > < / pre > < / div >
< p > < strong > Erwartete Ausgabe (Zeiten hardwareabhängig, Verhältnisse stabil):< / strong > < / p >
< pre > < code > ========================================================================================
MODELLAUFBAU: WO DIE ZEIT WIRKLICH HINGEHT
========================================================================================
Transportproblem mit m Werken und n Kunden -> m*n Variablen.
Groesse Variante Aufbau Loesen Aufbauanteil
----------------------------------------------------------------------------------------
40x40 = 1,600 Variablen A: OR-Tools, Add() je NB 0.031s 0.005s 87 %
B: COO in Schleifen 0.001s 0.009s 12 %
C: NumPy vektorisiert 0.001s 0.007s 14 %
D: Polars-Tabelle 0.001s 0.007s 12 %
-> Zielwert (alle gleich) 2107.32 Spanne 4.5e-13
----------------------------------------------------------------------------------------
120x120 = 14,400 Variablen A: OR-Tools, Add() je NB 0.287s 0.070s 80 %
B: COO in Schleifen 0.010s 0.051s 16 %
C: NumPy vektorisiert 0.001s 0.047s 3 %
D: Polars-Tabelle 0.001s 0.047s 3 %
-> Zielwert (alle gleich) 4693.67 Spanne 3.6e-12
----------------------------------------------------------------------------------------
250x250 = 62,500 Variablen A: OR-Tools, Add() je NB 1.284s 0.569s 69 %
B: COO in Schleifen 0.043s 0.215s 17 %
C: NumPy vektorisiert 0.003s 0.205s 1 %
D: Polars-Tabelle 0.003s 0.199s 1 %
-> Zielwert (alle gleich) 8316.24 Spanne 1.8e-12
----------------------------------------------------------------------------------------< / code > < / pre >
< p > < strong > Lesen Sie die letzte Spalte.< / strong > Bei 62 500 Variablen verbringt Variante A < strong > 69 % der Gesamtzeit damit, das Modell überhaupt aufzuschreiben< / strong > — und nur 31 % mit Rechnen. Die vektorisierten Varianten kehren das Verhältnis um: 1 % Aufbau, 99 % Solver. Erst dort ist ein schnellerer Solver überhaupt die richtige Stellschraube.< / p >
Grafik-Upgrade Stufe 1: elf neue Diagramme
Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind
19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern
19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38
Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal
darin. Von den 19 betreffen neun bereits vorhandene Diagramme.
Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem
Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen,
nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene
Grafik mit ihren eigenen Zahlen.
Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das
Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht
worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen),
harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die
fehlende Byte-Reproduzierbarkeit.
Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine
Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier
haben angeschlagen:
* Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. -
Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs.
* Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den
abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %,
gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht.
* Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95
MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte.
* Almgren-Chriss: ein echter Fehler im Buchprogramm.
DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr
Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren
kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten()
rechnet das Risiko mit lambda/2, die geschlossene Formel setzt
kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die
Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der
Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er
ist der, den das Kapitel abdruckt.
Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein
vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die
Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0).
Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre
Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine
Pruefung ueber alle elf zeigt null Fehlplatzierungen.
Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere().
networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt
unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren.
Veroeffentlicht: 25 Dateien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
< figure >
< img src = "bilder_04/kap_oekosystem_laufzeit.svg" alt = "Abb. 3.2: Dieselbe Aufgabe, zwei Arten sie aufzuschreiben — aufgeteilt in Aufbauzeit (Python) und Lösezeit (C++). Werte aus der Messung oben. Erzeugt von bilder_04/erzeuge_laufzeit_aufbau.py." / >
< figcaption aria-hidden = "true" > Abb. 3.2: Dieselbe Aufgabe, zwei Arten sie aufzuschreiben — aufgeteilt in Aufbauzeit (Python) und Lösezeit (C++). Werte aus der Messung oben. Erzeugt von < code > bilder_04/erzeuge_laufzeit_aufbau.py< / code > .< / figcaption >
< / figure >
< p > < strong > Was Sie in der Abbildung sehen.< / strong > Der rote Anteil ist die Zeit, in der der Solver noch gar nicht rechnet. Bei Variante A wächst er mit dem Modell, bei Variante C verschwindet er — dieselbe Mathematik, nur anders aufgeschrieben.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< blockquote >
< p > < strong > 💻 Code-Durchgang< / strong > < / p >
< table >
< colgroup >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< col style = "width: 33%" / >
< / colgroup >
< thead >
< tr class = "header" >
< th > Stelle< / th >
< th > Was passiert< / th >
< th > Warum es zählt< / th >
< / tr >
< / thead >
< tbody >
< tr class = "odd" >
< td > Aufwärmlauf vor der Messung< / td >
< td > jede Variante einmal mit 10× 10 laufen lassen< / td >
< td > Der erste Aufruf bezahlt Importe und einmalige Initialisierungen. Wer den mitmisst, vergleicht Startkosten statt Rechenarbeit — der häufigste Benchmark-Fehler überhaupt.< / td >
< / tr >
< tr class = "even" >
< td > < code > sp.kron(sp.identity(m), np.ones((1, n)))< / code > < / td >
< td > erzeugt die Angebotsmatrix in einem Aufruf< / td >
< td > Kronecker-Produkte sind das Werkzeug für „jede Gruppe bekommt eine Zeile“. Ein zweiter Blick lohnt: Genau dieses Muster deckt Zuordnungs-, Transport- und Schichtmodelle ab.< / td >
< / tr >
< tr class = "odd" >
< td > Variante B als COO-Tripel< / td >
< td > nicht als dichte Matrix< / td >
< td > Wäre B eine dichte Matrix, würde sie bei 62 500 Variablen 250 MB belegen — der Vergleich wüde dann Speicher statt Schleifen messen. Fairness im Benchmark heißt: nur eine Sache verändern.< / td >
< / tr >
< tr class = "even" >
< td > < code > assert spanne < 1e-6 * max(...)< / code > < / td >
< td > alle Varianten liefern denselben Zielwert< / td >
< td > Ohne diese Zeile misst man womöglich die Laufzeit von vier < em > verschiedenen< / em > Modellen. Ein Benchmark ohne Korrektheitsprüfung ist wertlos.< / td >
< / tr >
< tr class = "odd" >
< td > < code > anteil< / code > statt absoluter Zeiten< / td >
< td > Aufbauanteil in Prozent< / td >
< td > Absolute Zeiten hängen von der Hardware ab, das Verhältnis nicht. Es ist die Zahl, die die Entscheidung trägt.< / td >
< / tr >
< / tbody >
< / table >
< / blockquote >
< blockquote >
< p > < strong > 🎯 Merksatz< / strong > Fangen Sie mit der lesbarsten Variante an. Vektorisieren Sie erst, wenn Sie < strong > gemessen< / strong > haben, dass der Aufbau der Engpass ist — und messen Sie Aufbau und Lösen immer getrennt. Lesbarer Code, der schnell genug ist, schlägt schnellen Code, den niemand mehr versteht.< / p >
< / blockquote >
< hr / >
< h2 id = "sec:oekosystem-uebungsaufgaben" > 3.9 Übungsaufgaben< / h2 >
< blockquote >
< p > Lösungen: < a href = "anhang-loesungen.html#sec:loesungen-oekosystem" > Abschnitt A.3< / a > .< / p >
< / blockquote >
< p > < strong > Aufgabe 3.1 ⭐ — Solverwahl begründen.< / strong > Welche Bibliothek würden Sie wählen? Begründen Sie mit den drei Fragen aus < a href = "#sec:oekosystem-die-werkzeuge-im-vergleich" > Abschnitt 3.4< / a > : (a) Zuteilung von 300 Prüfungen auf 40 Räume und 12 Zeitfenster. (b) Mischungsproblem: günstigstes Tierfutter aus 8 Rohstoffen, Nährwertgrenzen. (c) Portfolio aus 200 Aktien, Risiko minimieren bei Mindestrendite. (d) Wie (c), aber höchstens 15 Titel im Depot. (e) Standortwahl: Welche 5 von 40 möglichen Lagern eröffnen? (f) Kalibrierung eines Modells mit 4 Parametern an Messdaten (Fehlerquadratsumme, nicht konvex).< / p >
< p > < strong > Aufgabe 3.2 ⭐⭐ — Modell übersetzen.< / strong > Formulieren Sie das Bäckerei-Problem aus < a href = "einfuehrung.html#kap-einfuehrung" > Kapitel 1< / a > in < strong > allen vier< / strong > Bibliotheken aus < a href = "#sec:oekosystem-ein-system-vier-programmieransaetze" > Abschnitt 3.5< / a > und prüfen Sie mit < code > assert< / code > , dass alle dasselbe Ergebnis liefern.< / p >
< p > < strong > Aufgabe 3.3 ⭐⭐ — CSR-Format von Hand.< / strong > Geben Sie < code > values< / code > , < code > indices< / code > und < code > starts< / code > für folgende Matrix an: < span class = "math display" > \mathbf{A}=\begin{pmatrix} 3 & 0 & 0 & 1 \\ 0 & 0 & 2 & 0 \\ 5 & 4 & 0 & 6\end{pmatrix}< / span > Wie viele Zahlen speichert CSR, wie viele die volle Matrix?< / p >
< p > < strong > Aufgabe 3.4 ⭐⭐ — Konvexitätsprüfung erleben.< / strong > Versuchen Sie, in CVXPY das Problem < span class = "math inline" > \min x^3< / span > u. d. N. < span class = "math inline" > -2 \le x \le 2< / span > zu lösen. Was passiert? Formulieren Sie anschließend < span class = "math inline" > \min x^2< / span > mit denselben Schranken. Erklären Sie den Unterschied in eigenen Worten.< / p >
< p > < strong > Aufgabe 3.5 ⭐⭐⭐ — Laufzeitvergleich.< / strong > Erzeugen Sie zufällige LPs wachsender Größe (< span class = "math inline" > n = 10, 50, 100, 500, 1000< / span > Variablen, < span class = "math inline" > m = n/2< / span > Nebenbedingungen) und messen Sie die Laufzeit von < code > linprog< / code > , < code > highspy< / code > und < code > cvxpy< / code > . Stellen Sie die Ergebnisse in einer Tabelle dar. (a) Ab welcher Größe zahlt sich < code > highspy< / code > aus? (b) Wie viel Zeit entfällt bei CVXPY auf den Modellaufbau, wie viel auf das eigentliche Lösen? (Tipp: < code > problem.solver_stats.solve_time< / code > .)< / p >
< p > < strong > Aufgabe 3.6 ⭐⭐⭐ — Eigene Entscheidungshilfe.< / strong > Erweitern Sie < code > Solver_Wahl.py< / code > um zwei weitere Kriterien: „Ist eine kommerzielle Lizenz verfügbar?“ und „Muss das Modell für Nicht-Programmierer lesbar sein?“. Ergänzen Sie passende Empfehlungen.< / p >
< hr / >
< h2 id = "sec:oekosystem-denkfehler" > 3.10 Finde den Denkfehler< / h2 >
< div class = "card card-denkfehler" >
< blockquote >
Sechster und siebter Fund: die letzten handgeschriebenen Nummern
Der Loesungsanhang trug 98 Marken der Bauart "**9.2 — Ansatz waehlen.**",
saemtlich aus Version 03, wo Unsicherheit Kapitel 9 war. Heute ist es
Kapitel 12, und die Aufgabe wird korrekt als "Aufgabe 12.2" gesetzt.
Das war keine Schoenheitsfrage: "Aufgabe 9.2" existiert wirklich - sie
gehoert zum Kapitel Metaheuristiken ("Zuggroesse und Temperatur"). Wer die
Loesung zu 9.2 nachschlug, landete beim falschen Thema. Beide bisherigen
Pruefungen liefen vorbei: Vor der Zahl steht kein Schluesselwort, und es ist
keine Tabellenzelle.
Zwei Messungen machten die Reparatur billig. In allen 15 nummerierten
Abschnitten gab es exakt so viele Loesungen wie Aufgaben, lueckenlos 1..n -
nur der Kapitelteil war falsch. Und die acht in Phase 3 ergaenzten Kapitel
machten es laengst richtig (**Titel.** ohne Nummer); der Anhang wurde also
nicht auf etwas Neues umgestellt, sondern auf das, was seine neueren Teile
schon taten.
nummeriere_marken() vergibt die Marken jetzt selbst. Der Praefix kommt NICHT
aus "# Anhang A:" - der Anhang ist eine Ueberschrift, seine Abschnitte
gehoeren aber zu 23 verschiedenen Kapiteln. Er kommt aus
{#sec:loesungen-X} -> {#kap:X}; diese Zuordnung gilt geprueft fuer alle 23.
Im Quelltext steht "**{loesung} — Titel.**".
Siebter Fund unterwegs: neun Verweise auf Denkfehler-Nummern. Saetze wie
"siehe Denkfehler 8.1" standen in alter Zaehlung - "8" war in Version 03 das
QP/NLP-Kapitel, heute ist es Graphen. Eine blosse Umnummerierung haette sie
stillschweigend woanders hin zeigen lassen; das Ziel wurde deshalb fuer jeden
einzelnen aus dem Zusammenhang bestimmt und auf {ref:sec:<kapitel>-denkfehler}
umgestellt. Einer steckte in einem Programm-Docstring und bekam nach Regel 12
den Kapitelnamen statt eines Verweises.
Eine bewusste Abweichung vom Plan: Der Denkfehler bekommt GAR KEINE Nummer.
Es gibt je Kapitel genau einen, die zweite Stelle waere immer .1, und er
steht ohnehin unter einer nummerierten Ueberschrift. Eine Nummer, die nie
variiert, holte nur die Fragilitaet zurueck, die hier beseitigt wird. Auch
das folgt den acht neuen Kapiteln, die ihren Denkfehler schon vorher nur mit
dem Titel des Raetsels ankuendigen.
--check bewacht jetzt alle Familien (Denkfehler N.M, Micro-Quiz N, **N.M —
im Anhang) und zaehlt zusaetzlich ab, dass jeder Loesungsabschnitt so viele
{loesung}-Marken hat wie sein Kapitel Aufgaben - das faengt eine vergessene
oder doppelte Loesung, was keine Textsuche leisten kann. Gegengetestet mit
vier kuenstlichen Fehlern auf einmal: alle vier gemeldet, nach dem Rueckbau
null.
Gegenprobe der Umstellung: Gesamtdokument vorher gesichert, nachher
verglichen. 168 geaenderte Zeilen, restlos einer Familie zuzuordnen - 118
Loesungsmarken (138 gesamt minus 20, die in den Kapiteln 1 bis 3 zufaellig
schon stimmten), 15 Anhang-Ueberschriften, 15 Kapitel-Callouts, 12
Micro-Quiz, 7 Zeilen mit Verweisen, 1 Docstring. Keine verschobene oder
inhaltlich veraenderte Zeile.
Ergebnis: 138 Aufgaben, 138 Loesungen, keine ohne Gegenstueck. Micro-Quiz
tragen die echten Kapitelnummern. 293 Abschnitte, 720 Querverweise, 327
Indexmarken, PDF unveraendert 725 Seiten, 33 pytest-Tests, 67 netzfreie
Programme fehlerfrei (die vier yfinance-Programme scheiterten am Rate-Limit
des Anbieters, nicht an dieser Aenderung).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:52:41 +02:00
< p > < strong > 🐛 Finde den Denkfehler: Der Solver, der angeblich dreimal schneller ist< / strong > < / p >
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.
2026-09-10 17:40:29 +02:00
< p > Ein Team vergleicht zwei Bibliotheken für sein < a href = "anhang-glossar.md#gloss:zuordnungsproblem" class = "glossar-link" > Zuordnungsproblem< / a > und schreibt ins Protokoll: < em > „CVXPY braucht 1,9 s, SciPy nur 0,6 s — wir setzen auf SciPy.“< / em > Gemessen wurde so:< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< div class = "sourceCode" id = "cb14" > < pre class = "sourceCode python" > < code class = "sourceCode python" > < span id = "cb14-1" > < a href = "#cb14-1" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > time< / span >
< span id = "cb14-2" > < a href = "#cb14-2" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb14-3" > < a href = "#cb14-3" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb14-4" > < a href = "#cb14-4" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > import< / span > cvxpy < span class = "im" > as< / span > cp< / span >
< span id = "cb14-5" > < a href = "#cb14-5" aria-hidden = "true" tabindex = "-1" > < / a > x < span class = "op" > =< / span > cp.Variable(< span class = "dv" > 200< / span > , nonneg< span class = "op" > =< / span > < span class = "va" > True< / span > )< / span >
< span id = "cb14-6" > < a href = "#cb14-6" aria-hidden = "true" tabindex = "-1" > < / a > problem < span class = "op" > =< / span > cp.Problem(cp.Minimize(kosten < span class = "op" > @< / span > x), [A < span class = "op" > @< / span > x < span class = "op" > ==< / span > b])< / span >
< span id = "cb14-7" > < a href = "#cb14-7" aria-hidden = "true" tabindex = "-1" > < / a > problem.solve()< / span >
< span id = "cb14-8" > < a href = "#cb14-8" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " CVXPY:" < / span > , time.perf_counter() < span class = "op" > -< / span > t0)< / span >
< span id = "cb14-9" > < a href = "#cb14-9" aria-hidden = "true" tabindex = "-1" > < / a > < / span >
< span id = "cb14-10" > < a href = "#cb14-10" aria-hidden = "true" tabindex = "-1" > < / a > t0 < span class = "op" > =< / span > time.perf_counter()< / span >
< span id = "cb14-11" > < a href = "#cb14-11" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "im" > from< / span > scipy.optimize < span class = "im" > import< / span > linprog< / span >
< span id = "cb14-12" > < a href = "#cb14-12" aria-hidden = "true" tabindex = "-1" > < / a > linprog(kosten, A_eq< span class = "op" > =< / span > A, b_eq< span class = "op" > =< / span > b, bounds< span class = "op" > =< / span > (< span class = "dv" > 0< / span > , < span class = "va" > None< / span > ), method< span class = "op" > =< / span > < span class = "st" > " highs" < / span > )< / span >
< span id = "cb14-13" > < a href = "#cb14-13" aria-hidden = "true" tabindex = "-1" > < / a > < span class = "bu" > print< / span > (< span class = "st" > " SciPy:" < / span > , time.perf_counter() < span class = "op" > -< / span > t0)< / span > < / code > < / pre > < / div >
< p > Beide finden dieselbe Lösung. Die Zeiten stimmen — auf diesem Rechner reproduzierbar. Trotzdem trägt die Entscheidung nicht.< / p >
< p > < strong > Ihre Aufgabe:< / strong > (a) Nennen Sie < strong > drei< / strong > Dinge, die diese Messung mitmisst, obwohl sie nichts mit der Lösegeschwindigkeit zu tun haben. (b) Warum ist gerade bei CVXPY der gemessene Wert besonders irreführend, wenn das Modell später in einer Schleife 60-mal mit wechselnden Daten gelöst wird? (c) Wie sähe eine Messung aus, die die Frage des Teams tatsächlich beantwortet?< / p >
< p > < em > Auflösung: < a href = "anhang-loesungen.html#sec:loesungen-oekosystem" > Abschnitt A.3< / a > .< / em > < / p >
< / blockquote >
< / div >
< hr / >
< h2 id = "sec:oekosystem-quiz" > 3.11 Micro-Quiz< / h2 >
< div class = "card card-quiz" >
< blockquote >
< p > < strong > ❓ Micro-Quiz 3: Drei Fragen zum Selbstcheck< / strong > < / p >
< p > Genau eine Antwort ist jeweils richtig. Auflösung in < a href = "anhang-loesungen.html#quiz-loesung-oekosystem" > Anhang A< / a > .< / p >
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.
2026-09-10 17:40:29 +02:00
< p > < strong > 1. Sie sollen 300 Prüfungen auf 40 Räume und 12 Zeitfenster verteilen, mit Regeln wie „diese beiden Klausuren nicht gleichzeitig“. Welches Werkzeug passt?< / strong > (a) CVXPY — es prüft die < a href = "anhang-glossar.md#gloss:konvexitaet" class = "glossar-link" > Konvexität< / a > automatisch. (b) OR-Tools CP-SAT — diskrete Zuweisung mit logischen Regeln ist genau sein Gebiet. (c) < code > scipy.optimize.minimize< / code > — es kann beliebige Zielfunktionen.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< p > < strong > 2. Ihr Modell mit 80 000 Variablen braucht 40 Sekunden, davon 32 Sekunden bis zum ersten Solver-Aufruf. Was ist die wirksamste Maßnahme?< / strong > (a) Einen kommerziellen Solver lizenzieren. (b) Das Zeitlimit des Solvers heraufsetzen. (c) Den Modellaufbau vektorisieren — der Solver ist mit 8 Sekunden gar nicht der Engpass.< / p >
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.
2026-09-10 17:40:29 +02:00
< p > < strong > 3. Was haben < code > scipy.optimize.linprog< / code > , < code > highspy< / code > , CVXPY (mit Standardeinstellung) und Pyomo (mit < code > appsi_highs< / code > ) gemeinsam?< / strong > (a) Sie implementieren jeweils einen eigenen < a href = "anhang-glossar.md#gloss:simplex-algorithmus" class = "glossar-link" > Simplex-Algorithmus< / a > in Python. (b) Sie sind Modellierungsschichten über demselben C++-Solver HiGHS — die Rechenleistung ist dieselbe, nur die Schreibweise unterscheidet sich. (c) Sie können alle sowohl konvexe als auch nicht-konvexe Probleme global lösen.< / p >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / blockquote >
< / div >
< hr / >
< h2 id = "sec:oekosystem-selbsttest" > 3.12 Selbsttest< / h2 >
< blockquote >
< p > Antworten: < a href = "anhang-loesungen.html#selbsttest-loesung-oekosystem" > Anhang A< / a > .< / p >
< / blockquote >
< ol type = "1" >
< li > Welche zwei Schichten hat eine moderne OR-Architektur, und wozu dient die Trennung?< / li >
< li > Warum wirft CVXPY bei < span class = "math inline" > \min x^3< / span > einen Fehler, < code > scipy.optimize.minimize< / code > aber nicht?< / li >
< li > Was speichert das CSR-Format, und warum ist es bei großen Modellen entscheidend?< / li >
< li > Warum ist CVXPY im Laufzeitvergleich (< a href = "#sec:oekosystem-ein-system-vier-programmieransaetze" > Abschnitt 3.5< / a > ) das langsamste Werkzeug — und warum ist das trotzdem kein Argument gegen seinen Einsatz?< / li >
< li > Nennen Sie zwei Situationen, in denen CP-SAT die falsche Wahl wäre.< / li >
< / ol >
< hr / >
< h2 id = "sec:oekosystem-zusammenfassung" > 3.13 Zusammenfassung< / h2 >
< ul >
< li > < strong > Zwei Schichten:< / strong > Sie formulieren in Python, ein C++-Solver rechnet. Beides ist austauschbar.< / li >
< li > < strong > Drei Fragen< / strong > genügen zur Solverwahl: diskrete Entscheidungen? lineare Zielfunktion? Routing?< / li >
< li > < strong > Dasselbe Modell, vier Wege, ein Ergebnis< / strong > — nutzen Sie das als Prüfmittel: Wenn zwei Bibliotheken verschiedene Optima melden, ist eine Ihrer Formulierungen falsch.< / li >
< li > < strong > CVXPYs Konvexitätsprüfung ist ein Schutzmechanismus< / strong > , keine Schikane.< / li >
< li > < strong > Sechs Modellierungsschichten, eine Handvoll Solver.< / strong > Pyomo (Mengen und Indizes) und Linopy (beschriftete Arrays) ergänzen die vier Grundwerkzeuge, sobald Modelle industrielle Größe erreichen — sie rechnen aber alle mit denselben C++-Engines.< / li >
< li > < strong > Messen Sie Aufbau- und Lösezeit getrennt.< / strong > Bei 62 500 Variablen entfallen in der bequemen Schreibweise rund zwei Drittel der Zeit auf den Modellaufbau. Vektorisierung mit NumPy oder Polars dreht das Verhältnis um — ein schnellerer Solver hätte es nicht getan.< / li >
< li > < strong > Rechnen Sie das Ergebnis wenn möglich von Hand nach< / strong > — bei kleinen Instanzen ist das in Minuten erledigt und deckt Modellfehler auf, die kein Solver melden kann.< / li >
< / ul >
< p > < strong > Ausblick.< / strong > < a href = "lp.html#teil-kernverfahren" > Teil II< / a > beginnt mit dem Kernverfahren des Operations Research: der linearen Programmierung. Wir bauen den Simplex-Algorithmus selbst, um zu verstehen, woher Schattenpreise kommen — und was sie wirtschaftlich bedeuten.< / p >
< / article >
< button type = "button" class = "fortschritt-knopf" data-kapitel = "oekosystem.html" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-check" > < / use > < / svg > < span > Als gelesen markieren< / span > < / button >
2026-09-10 00:07:01 +02:00
< nav class = "prev-next" > < a class = "prev-next-knopf prev-next-prev" href = "fundament.html" > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-chevron-left" > < / use > < / svg > < span > < small > Zurück< / small > Kapitel 2: Das mathematische Fundament — Vektoren, Matrizen, Konvexität{idx:Konvexität}< / span > < / a > < a class = "prev-next-knopf prev-next-next" href = "modellierung.html" > < span > < small > Weiter< / small > Kapitel 4: Vom Management-Wunsch zum Modell< / span > < svg class = "icon" aria-hidden = "true" > < use href = "#icon-chevron-right" > < / use > < / svg > < / a > < / nav >
Version 04 als eigenes Repository
Erster Commit des Strangs "Optimierte Entscheidungsfindung mit Python"
(Version 04). Die Historie der 71 Commits bis zur Trennung bleibt im
uebergeordneten Repository OR_mit_Python liegen, das ab jetzt nur noch
Version_03 (eingefroren) verwaltet und Version_04/ ignoriert.
Bewusst kein "git subtree split": Der Pfad Version_04/ existiert erst seit
der Verzeichnistrennung, ein Split braechte daher nur 7 der 41 einschlaegigen
Commits - eine Teilhistorie, die vollstaendig aussieht und es nicht ist.
Stand: 5 Teile, 23 Kapitel, 5 Anhaenge, 292 Abschnitte, 703 Querverweise,
325 Indexmarken, 73 Beispielprogramme, 32 SVGs, 4 Plotly-Figuren,
25 Notebooks, PDF mit 715 Seiten.
Zusaetzlich in diesem Commit:
* pyproject.toml mit Abhaengigkeitsgruppen finance, large-scale, api,
figures, dev, empfehlungen. Die abgedruckte requirements.txt bleibt
unveraendert daneben bestehen. ortools steht in der Grundausstattung,
highspy erst in [large-scale] - so kann der HiGHS-Symbolkonflikt bei der
schlanken Installation gar nicht erst auftreten.
* Dabei zwei Funde: graphviz wird von erzeuge_architektur_diagramme.py
importiert, fehlt aber in requirements.txt (jetzt in [figures]); pymoo
steht in requirements.txt, wird aber von keinem Programm importiert,
sondern nur im Kapitel Metaheuristiken empfohlen (jetzt in
[empfehlungen]).
* NEUER_TITEL.md nach Kritik_und_Verbesserungsvorschlaege/ verschoben - es
ist die Vorlage des Titelblatts, kein Bestandteil des Werks. Die beiden
Fundstellen in PROGRESS.md und erzeuge_titelseite.py nachgezogen.
* PROGRESS.md nannte noch den Untertitel der ersten Fassung; auf den
tatsaechlichen aus erzeuge_titelseite.py korrigiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:20:09 +02:00
< / main >
< / div >
< footer class = "site-footer" >
< p > © Dieter Schlüter · < a href = "gesamtdokument.html" > Gesamtdokument< / a > ·
< a href = "programme.html" > Beispielprogramme< / a > < / p >
< / footer >
< script defer src = "assets/search-index.js" > < / script >
< script defer src = "assets/programme.js" > < / script >
< script defer src = "assets/site.js" > < / script >
< / body >
< / html >