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>
159
bilder_04/erzeuge_architektur_diagramme.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_architektur_diagramme.py
|
||||
"""
|
||||
Erzeugt die beiden Architekturdiagramme fuer den Weg vom Skript zum Betrieb:
|
||||
|
||||
bilder_04/kap_praxisfallen_schichten.svg / .png Schichten von or_kern.py
|
||||
bilder_04/kap_testing_dienst.svg / .png der Dienst und sein Ablauf
|
||||
|
||||
Beide bilden ab, was in den Programmen tatsaechlich steht - nicht ein
|
||||
allgemeines Architekturschema:
|
||||
|
||||
Schichten - die sechs Abschnitte von or_kern.py, in der Reihenfolge ihrer
|
||||
Abhaengigkeiten. Die Aussage steckt in der Pfeilrichtung: Alles
|
||||
zeigt nach innen auf die Domaene, und die Domaene zeigt auf
|
||||
nichts. Genau deshalb laesst sich der Solver austauschen, ohne
|
||||
das Fachmodell anzufassen.
|
||||
Dienst - der Ablauf in Optimierungsdienst.py. Die Aussage ist die
|
||||
202-Antwort: Wer eine Optimierung synchron beantwortet, baut
|
||||
einen Dienst, der unter Last umkippt.
|
||||
|
||||
Beide entstehen mit Graphviz, in derselben Palette wie die
|
||||
Entscheidungsdiagramme (siehe erzeuge_solverwahl_diagramme.py).
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_architektur_diagramme.py
|
||||
|
||||
Benoetigt: graphviz (Python-Paket) und das Kommandozeilenwerkzeug 'dot'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import graphviz
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, VIOLETT, GRUEN, AMBER = (
|
||||
"#4338ca", "#0891b2", "#7c3aed", "#059669", "#b45309")
|
||||
TINT = {INDIGO: "#eef2ff", CYAN: "#ecfeff", VIOLETT: "#f5f3ff",
|
||||
GRUEN: "#ecfdf5", AMBER: "#fffbeb"}
|
||||
|
||||
|
||||
def neuer_graph(titel: str, richtung: str = "TB") -> graphviz.Digraph:
|
||||
punkt = graphviz.Digraph(format="svg")
|
||||
punkt.attr(rankdir=richtung, splines="polyline", nodesep="0.35",
|
||||
ranksep="0.5", fontname="DejaVu Sans", label=titel, labelloc="t",
|
||||
fontsize="13", bgcolor="transparent")
|
||||
punkt.attr("node", fontname="DejaVu Sans", fontsize="10")
|
||||
punkt.attr("edge", fontname="DejaVu Sans", fontsize="9", color="#5c6380")
|
||||
return punkt
|
||||
|
||||
|
||||
def kasten(punkt: graphviz.Digraph, name: str, text: str, farbe: str,
|
||||
form: str = "box") -> None:
|
||||
punkt.node(name, text, shape=form, style="filled,rounded",
|
||||
fillcolor=TINT[farbe], color=farbe, penwidth="2.0",
|
||||
margin="0.16,0.09")
|
||||
|
||||
|
||||
def schreibe(punkt: graphviz.Digraph, name: str) -> None:
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
punkt.format = endung
|
||||
pfad = punkt.render(os.path.join(BILDER, name), cleanup=True)
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
# --- 1. Die Schichten von or_kern.py ----------------------------------------
|
||||
|
||||
def schichten() -> None:
|
||||
punkt = neuer_graph("Die Schichten von or_kern.py — alle Pfeile zeigen nach innen")
|
||||
|
||||
kasten(punkt, "io", "Ein- und Ausgabe\n"
|
||||
"lade_produktionsproblem() · schreibe_ergebnis()\n"
|
||||
"Excel, CSV — die Wirklichkeit des Anwenders", AMBER)
|
||||
kasten(punkt, "bau", "Modellbauer — austauschbar\n"
|
||||
"loese_mit_glop() · loese_mit_scipy()\n"
|
||||
"je Solver einer, gleiche Signatur", VIOLETT)
|
||||
kasten(punkt, "pruef", "Abnahmeprüfung\npruefe_loesung()\n"
|
||||
"prüft die Lösung OHNE Solver", GRUEN)
|
||||
kasten(punkt, "erg", "Ergebnis — solverunabhängig\n"
|
||||
"SolverStatus · Loesung\n"
|
||||
"fünf Bibliotheken, eine Sprache", CYAN)
|
||||
kasten(punkt, "dom", "Domäne\nProdukt · Produktionsproblem\n"
|
||||
"validiert (Pydantic), kennt keinen Solver", INDIGO)
|
||||
|
||||
punkt.edge("io", "dom", label="liest ein, schreibt zurück")
|
||||
punkt.edge("bau", "dom", label="baut das Modell aus")
|
||||
punkt.edge("bau", "erg", label="liefert")
|
||||
punkt.edge("pruef", "dom", label="prüft gegen")
|
||||
punkt.edge("pruef", "erg", label="prüft")
|
||||
punkt.edge("erg", "dom", style="invis") # haelt die Ebenen sauber
|
||||
|
||||
with punkt.subgraph() as gleich:
|
||||
gleich.attr(rank="same")
|
||||
gleich.node("bau")
|
||||
gleich.node("pruef")
|
||||
|
||||
punkt.node("regel",
|
||||
"Die Domäne importiert nichts von außen.\\l"
|
||||
"Deshalb kostet ein Solverwechsel eine Funktion —\\l"
|
||||
"und kein einziges Fachdetail.\\l",
|
||||
shape="note", style="filled", fillcolor="#f8fafc",
|
||||
color="#94a3b8", fontsize="9", margin="0.18,0.12")
|
||||
punkt.edge("dom", "regel", style="dashed", color="#94a3b8", arrowhead="none")
|
||||
|
||||
schreibe(punkt, "kap_praxisfallen_schichten")
|
||||
|
||||
|
||||
# --- 2. Der Optimierungsdienst ----------------------------------------------
|
||||
|
||||
def dienst() -> None:
|
||||
punkt = neuer_graph("Optimierungsdienst: warum die Antwort 202 lautet, nicht 200")
|
||||
|
||||
kasten(punkt, "klient", "Aufrufer\n(curl, Fachanwendung)", CYAN)
|
||||
kasten(punkt, "post", "POST /plaene\nJSON mit dem Problem", INDIGO)
|
||||
kasten(punkt, "valid", "Pydantic prüft\ndie Eingabe", VIOLETT, form="diamond")
|
||||
kasten(punkt, "fehler", "422\nFehler benannt,\nbevor ein Solver läuft", AMBER)
|
||||
kasten(punkt, "an", "202 Accepted\n+ Auftrags-ID", GRUEN)
|
||||
kasten(punkt, "rechne", "Hintergrundrechnung\nrechne() → or_kern", VIOLETT)
|
||||
kasten(punkt, "get", "GET /plaene/{id}\nlaeuft · fertig · fehlgeschlagen", INDIGO)
|
||||
kasten(punkt, "gesund", "GET /gesundheit\nfür die Überwachung", CYAN)
|
||||
|
||||
punkt.edge("klient", "post")
|
||||
punkt.edge("post", "valid")
|
||||
punkt.edge("valid", "fehler", label="ungültig")
|
||||
punkt.edge("valid", "an", label="gültig")
|
||||
punkt.edge("an", "rechne", label="startet")
|
||||
# constraint=false: Die Rueckmeldung darf die Rangfolge nicht umsortieren,
|
||||
# sonst wandert der Aufrufer unter den Dienst.
|
||||
punkt.edge("an", "klient", label="sofort zurück", style="dashed",
|
||||
constraint="false")
|
||||
punkt.edge("klient", "get", label="fragt nach")
|
||||
punkt.edge("rechne", "get", label="hinterlegt das Ergebnis", style="dashed")
|
||||
punkt.edge("klient", "gesund", style="dotted")
|
||||
|
||||
with punkt.subgraph() as gleich:
|
||||
gleich.attr(rank="same")
|
||||
gleich.node("fehler")
|
||||
gleich.node("an")
|
||||
|
||||
punkt.node("regel",
|
||||
"Eine Optimierung dauert Sekunden bis Minuten.\\l"
|
||||
"Synchron beantwortet, blockiert sie den Dienst\\l"
|
||||
"und läuft in jedes Gateway-Timeout.\\l",
|
||||
shape="note", style="filled", fillcolor="#f8fafc",
|
||||
color="#94a3b8", fontsize="9", margin="0.18,0.12")
|
||||
punkt.edge("rechne", "regel", style="dashed", color="#94a3b8",
|
||||
arrowhead="none")
|
||||
|
||||
schreibe(punkt, "kap_testing_dienst")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
schichten()
|
||||
dienst()
|
||||
192
bilder_04/erzeuge_branch_and_bound.py
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_branch_and_bound.py
|
||||
"""
|
||||
Erzeugt den Branch-and-Bound-Suchbaum zum MILP-Kapitel:
|
||||
|
||||
bilder_04/kap_milp_suchbaum.svg / .png
|
||||
|
||||
Gezeichnet wird der Baum der Handrechnung des Kapitels:
|
||||
|
||||
max 5 x1 + 4 x2 u.d.N. 6 x1 + 4 x2 <= 24, x1 + 2 x2 <= 6, x ganzzahlig
|
||||
|
||||
Der Baum wird NICHT abgeschrieben, sondern gerechnet: Eine kleine
|
||||
Branch-and-Bound-Schleife loest die LP-Relaxationen mit scipy.linprog,
|
||||
verzweigt ueber die erste gebrochene Variable und kappt nach denselben drei
|
||||
Regeln, die der Kapiteltext nennt (unzulaessig, Schranke schlechter als der
|
||||
Incumbent, Loesung bereits ganzzahlig). Am Ende prueft das Skript, dass Optimum
|
||||
und Zahl der geloesten LPs mit dem Buchtext uebereinstimmen - stimmt etwas
|
||||
nicht, bricht es ab, statt ein plausibles Bild zu malen.
|
||||
|
||||
Warum ueberhaupt neu: Das Vorgaengerbild zeigte einen Baum mit voellig anderen
|
||||
Zahlen (Wurzel x1 = 2,7, Schranke 150,4, Incumbent 138) zu einem Modell, das
|
||||
nirgends im Buch steht.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_branch_and_bound.py
|
||||
|
||||
Benoetigt: numpy, scipy, graphviz (Python-Paket) und das Werkzeug 'dot'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import graphviz
|
||||
import numpy as np
|
||||
from scipy.optimize import linprog
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, VIOLETT, GRUEN, AMBER = (
|
||||
"#4338ca", "#0891b2", "#7c3aed", "#059669", "#b45309")
|
||||
TINT = {INDIGO: "#eef2ff", CYAN: "#ecfeff", VIOLETT: "#f5f3ff",
|
||||
GRUEN: "#ecfdf5", AMBER: "#fffbeb"}
|
||||
|
||||
# Modell der Handrechnung
|
||||
C = np.array([5.0, 4.0])
|
||||
A = np.array([[6.0, 4.0], [1.0, 2.0]])
|
||||
B = np.array([24.0, 6.0])
|
||||
TOL = 1e-7
|
||||
|
||||
|
||||
def loese(untergrenzen, obergrenzen):
|
||||
"""LP-Relaxation mit zusaetzlichen Schranken. Liefert (x, Z) oder (None, None)."""
|
||||
ergebnis = linprog(c=-C, A_ub=A, b_ub=B,
|
||||
bounds=list(zip(untergrenzen, obergrenzen)),
|
||||
method="highs")
|
||||
if not ergebnis.success:
|
||||
return None, None
|
||||
return np.asarray(ergebnis.x), float(-ergebnis.fun)
|
||||
|
||||
|
||||
def erste_gebrochene(x):
|
||||
for j, wert in enumerate(x):
|
||||
if abs(wert - round(wert)) > 1e-6:
|
||||
return j
|
||||
return None
|
||||
|
||||
|
||||
def zahl(wert: float) -> str:
|
||||
"""Deutsche Schreibweise, hoechstens zwei Nachkommastellen.
|
||||
|
||||
Das '+ 0.0' faengt die negative Null ab: linprog liefert fuer x2 = 0
|
||||
gelegentlich -0.0, und '(4; -0)' im Buch waere ein Schoenheitsfehler mit
|
||||
Ansage.
|
||||
"""
|
||||
text = f"{wert + 0.0:.2f}".rstrip("0").rstrip(".")
|
||||
if text in ("-0", "-"):
|
||||
text = "0"
|
||||
return text.replace(".", ",")
|
||||
|
||||
|
||||
def branch_and_bound():
|
||||
"""Baut den Suchbaum und liefert (Knotenliste, Kanten, Optimum, Zielwert).
|
||||
|
||||
Die Reihenfolge - erst der Ast mit der kleineren Obergrenze, dann der
|
||||
andere - entspricht der Handrechnung: Ast A (x2 <= 1) wird vor Ast B
|
||||
(x2 >= 2) bearbeitet, weshalb der Incumbent aus A stammt und B daran
|
||||
scheitert.
|
||||
"""
|
||||
knoten, kanten = [], []
|
||||
incumbent, incumbent_x = -np.inf, None
|
||||
# TIEFENSUCHE (Stapel, nicht Warteschlange). Das ist keine Kosmetik: Nur so
|
||||
# ist Ast A samt seinen Blaettern fertig, bevor Ast B an die Reihe kommt -
|
||||
# und nur deshalb scheitert B am Incumbent 20, wie es der Kapiteltext
|
||||
# beschreibt. Breitensuche wuerde B zum Incumbent machen und die Aussage
|
||||
# des Bildes umdrehen.
|
||||
stapel = [("P₀", None, [0.0, 0.0], [np.inf, np.inf], None)]
|
||||
zaehler = 0
|
||||
|
||||
while stapel:
|
||||
name, elter, unten, oben, kantentext = stapel.pop()
|
||||
x, z = loese(unten, oben)
|
||||
zaehler += 1
|
||||
if elter is not None:
|
||||
kanten.append((elter, name, kantentext))
|
||||
|
||||
if x is None:
|
||||
knoten.append((name, "unzulässig — gekappt", AMBER, None))
|
||||
continue
|
||||
if z < incumbent - TOL:
|
||||
knoten.append((name, f"Z = {zahl(z)} ≤ Incumbent {zahl(incumbent)}\\n"
|
||||
"→ gekappt", AMBER, None))
|
||||
continue
|
||||
|
||||
j = erste_gebrochene(x)
|
||||
if j is None:
|
||||
besser = z > incumbent + TOL
|
||||
if besser:
|
||||
incumbent, incumbent_x = z, x
|
||||
knoten.append((name,
|
||||
f"x = ({zahl(x[0])}; {zahl(x[1])}), Z = {zahl(z)}\\n"
|
||||
+ ("ganzzahlig — neuer Incumbent" if besser
|
||||
else f"ganzzahlig, aber ≤ Incumbent {zahl(incumbent)}"),
|
||||
GRUEN if besser else AMBER, z))
|
||||
continue
|
||||
|
||||
knoten.append((name,
|
||||
f"x = ({zahl(x[0])}; {zahl(x[1])}), Schranke Z ≤ {zahl(z)}\\n"
|
||||
f"x{j + 1} gebrochen — verzweigen", INDIGO if elter is None else CYAN,
|
||||
z))
|
||||
grenze = float(np.floor(x[j]))
|
||||
links_oben = list(oben); links_oben[j] = grenze
|
||||
rechts_unten = list(unten); rechts_unten[j] = grenze + 1
|
||||
# Zuerst den ">="-Ast auflegen, damit der "<="-Ast oben liegt und
|
||||
# als Erster bearbeitet wird - die Reihenfolge der Handrechnung.
|
||||
# Namen wie im Kapiteltext: die Wurzel verzweigt in Ast A und Ast B,
|
||||
# deren Kinder heissen A1/A2 bzw. B1/B2.
|
||||
links, rechts = ("A", "B") if elter is None else (f"{name}1", f"{name}2")
|
||||
stapel.append((rechts, name, rechts_unten, list(oben),
|
||||
f"x{j + 1} ≥ {int(grenze) + 1}"))
|
||||
stapel.append((links, name, list(unten), links_oben,
|
||||
f"x{j + 1} ≤ {int(grenze)}"))
|
||||
|
||||
return knoten, kanten, incumbent_x, incumbent, zaehler
|
||||
|
||||
|
||||
def zeichne(knoten, kanten, bestes, zielwert, geloest) -> None:
|
||||
punkt = graphviz.Digraph(format="svg")
|
||||
punkt.attr(rankdir="TB", splines="polyline", nodesep="0.45", ranksep="0.55",
|
||||
fontname="DejaVu Sans", bgcolor="transparent", fontsize="12",
|
||||
labelloc="t",
|
||||
label=(f"Branch-and-Bound: {geloest} gelöste LPs statt aller "
|
||||
f"ganzzahligen Punkte\\n"
|
||||
f"max 5x₁ + 4x₂ u.d.N. 6x₁+4x₂ ≤ 24, x₁+2x₂ ≤ 6"))
|
||||
punkt.attr("node", fontname="DejaVu Sans", fontsize="10")
|
||||
punkt.attr("edge", fontname="DejaVu Sans", fontsize="9", color="#5c6380")
|
||||
|
||||
for name, text, farbe, _ in knoten:
|
||||
beschriftung = f"{name}\\n{text}"
|
||||
punkt.node(name, beschriftung, shape="box", style="filled,rounded",
|
||||
fillcolor=TINT[farbe], color=farbe, penwidth="2.0",
|
||||
margin="0.16,0.09")
|
||||
for von, nach, text in kanten:
|
||||
punkt.edge(von, nach, label=text or "")
|
||||
|
||||
punkt.node("ergebnis",
|
||||
f"Optimum: x* = ({zahl(bestes[0])}; {zahl(bestes[1])}), "
|
||||
f"Z* = {zahl(zielwert)}",
|
||||
shape="note", style="filled", fillcolor="#f8fafc",
|
||||
color="#94a3b8", fontsize="10", margin="0.18,0.12")
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
punkt.format = endung
|
||||
pfad = punkt.render(os.path.join(BILDER, "kap_milp_suchbaum"), cleanup=True)
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
knoten, kanten, bestes, zielwert, geloest = branch_and_bound()
|
||||
print(f"{len(knoten)} Knoten, {geloest} geloeste LPs")
|
||||
for name, text, _, _ in knoten:
|
||||
print(f" {name:5} {text.replace(chr(92) + 'n', ' | ')}")
|
||||
print(f"Optimum: ({bestes[0]:g}; {bestes[1]:g}), Z = {zielwert:g}")
|
||||
|
||||
# Gegenprobe gegen die Handrechnung im Kapitel
|
||||
if not (np.allclose(bestes, [4.0, 0.0]) and abs(zielwert - 20.0) < TOL):
|
||||
raise SystemExit("FEHLER: Ergebnis weicht von der Handrechnung ab "
|
||||
"(erwartet x* = (4; 0), Z* = 20).")
|
||||
zeichne(knoten, kanten, bestes, zielwert, geloest)
|
||||
275
bilder_04/erzeuge_effizienzlinie.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_effizienzlinie.py
|
||||
"""
|
||||
Erzeugt die Figur "Was die Hausregeln kosten" zum Kapitel Markowitz:
|
||||
|
||||
bilder_04/kap_markowitz_restriktionen.svg statisch, fuer das PDF
|
||||
bilder_04/kap_markowitz_restriktionen.png Rasterfassung
|
||||
bilder_04/plotly/kap_markowitz_restriktionen.html interaktiv, fuer die Website
|
||||
|
||||
Gezeigt werden zwei Effizienzlinien desselben Universums: einmal ohne
|
||||
Beschraenkungen, einmal mit den institutionellen Regeln aus dem Kapitel
|
||||
(hoechstens 20 % je Titel, Sektorgrenzen). Der senkrechte Abstand zwischen
|
||||
beiden Kurven IST der Preis der Hausregeln - in Renditepunkten, bei gleichem
|
||||
Risiko.
|
||||
|
||||
Warum eine feste Instanz und nicht die Kursdaten aus Markowitz_CVXPY.py:
|
||||
Jenes Programm laedt Livedaten. Ein Diagramm daraus saehe an jedem Tag anders
|
||||
aus, und der abgedruckte Text koennte nicht dazu passen. Hier wird deshalb ein
|
||||
FESTES Universum aus einem Faktormodell erzeugt (Marktfaktor, vier
|
||||
Sektorfaktoren, titelspezifisches Rauschen) - dieselbe Struktur wie im Kapitel,
|
||||
dieselben Regeln, aber reproduzierbar. Die Zahlen des Livelaufs weichen ab, die
|
||||
Aussage nicht.
|
||||
|
||||
Die interaktive Fassung zeigt im Hover, was das statische Bild nicht kann: die
|
||||
GEWICHTE des jeweiligen Portfolios. Damit wird sichtbar, wie sich die
|
||||
Zusammensetzung entlang der Linie verschiebt - und woran die beschraenkte
|
||||
Fassung scheitert, sobald ein einzelner Titel an seine 20-%-Grenze stoesst.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_effizienzlinie.py
|
||||
|
||||
Benoetigt: numpy, cvxpy, matplotlib, plotly
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import cvxpy as cp
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
PLOTLY_VERZ = os.path.join(BILDER, "plotly")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRUEN = "#4338ca", "#0891b2", "#b45309", "#059669"
|
||||
|
||||
# Struktur wie in Markowitz_CVXPY.py - zehn Titel in vier Sektoren
|
||||
TITEL = ["AAPL", "MSFT", "NVDA", "AMZN", "JNJ", "PFE", "JPM", "GS", "XOM", "CVX"]
|
||||
SEKTOR_VON = ["Technologie"] * 4 + ["Gesundheit"] * 2 + ["Finanzen"] * 2 + ["Energie"] * 2
|
||||
SEKTORGRENZEN = {"Technologie": 0.35, "Gesundheit": 0.40,
|
||||
"Finanzen": 0.40, "Energie": 0.40}
|
||||
MAX_GEWICHT = 0.20
|
||||
PUNKTE = 40
|
||||
|
||||
|
||||
def universum(saat: int = 3) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Erwartete Renditen und Kovarianzmatrix aus einem Faktormodell.
|
||||
|
||||
Jeder Titel haengt am Markt, an seinem Sektor und an eigenem Rauschen.
|
||||
Genau diese Struktur macht Diversifikation ueberhaupt moeglich: Titel
|
||||
desselben Sektors laufen staerker im Gleichschritt als Titel verschiedener
|
||||
Sektoren - und nur der zweite Fall traegt.
|
||||
"""
|
||||
rng = np.random.default_rng(saat)
|
||||
n = len(TITEL)
|
||||
sektoren = sorted(set(SEKTOR_VON))
|
||||
ladung_markt = rng.uniform(0.7, 1.3, n)
|
||||
ladung_sektor = rng.uniform(0.4, 0.9, n)
|
||||
eigen = rng.uniform(0.10, 0.26, n)
|
||||
|
||||
sigma_markt, sigma_sektor = 0.16, 0.12
|
||||
kovarianz = np.outer(ladung_markt, ladung_markt) * sigma_markt ** 2
|
||||
for sektor in sektoren:
|
||||
maske = np.array([s == sektor for s in SEKTOR_VON], dtype=float)
|
||||
gewicht = ladung_sektor * maske
|
||||
kovarianz += np.outer(gewicht, gewicht) * sigma_sektor ** 2
|
||||
kovarianz += np.diag(eigen ** 2)
|
||||
|
||||
# Erwartete Renditen: Marktprämie plus etwas titelspezifische Streuung
|
||||
mu = 0.03 + 0.055 * ladung_markt + rng.normal(0, 0.012, n)
|
||||
return mu, kovarianz
|
||||
|
||||
|
||||
def sektormasken() -> dict[str, np.ndarray]:
|
||||
return {sektor: np.array([s == sektor for s in SEKTOR_VON], dtype=float)
|
||||
for sektor in SEKTORGRENZEN}
|
||||
|
||||
|
||||
def linie(mu: np.ndarray, kovarianz: np.ndarray, beschraenkt: bool):
|
||||
"""Effizienzlinie ueber das epsilon-Constraint-Verfahren.
|
||||
|
||||
Fuer jede Zielrendite wird das minimale Risiko gesucht. Das ist dasselbe
|
||||
Verfahren, mit dem im Kapitel Mehrziel die Pareto-Front entsteht - eine
|
||||
Effizienzlinie IST eine Pareto-Front aus Rendite und Risiko.
|
||||
"""
|
||||
n = len(mu)
|
||||
w = cp.Variable(n, nonneg=True)
|
||||
ziel_rendite = cp.Parameter(nonneg=True)
|
||||
|
||||
# Cholesky statt quad_form: die 2-Norm eines affinen Ausdrucks ist
|
||||
# zweifelsfrei DCP (siehe die Fehlerdiagnose zu DCPError).
|
||||
wurzel = np.linalg.cholesky(kovarianz + 1e-12 * np.eye(n))
|
||||
|
||||
bedingungen = [cp.sum(w) == 1, mu @ w >= ziel_rendite]
|
||||
if beschraenkt:
|
||||
bedingungen.append(w <= MAX_GEWICHT)
|
||||
for sektor, maske in sektormasken().items():
|
||||
bedingungen.append(maske @ w <= SEKTORGRENZEN[sektor])
|
||||
|
||||
problem = cp.Problem(cp.Minimize(cp.norm2(wurzel.T @ w)), bedingungen)
|
||||
|
||||
# Der obere Rand ist NICHT max(mu): Unter Restriktionen ist der beste
|
||||
# Einzeltitel gar nicht voll kaufbar. Also wird er ausgerechnet.
|
||||
w_max = cp.Variable(n, nonneg=True)
|
||||
obere = [cp.sum(w_max) == 1]
|
||||
if beschraenkt:
|
||||
obere.append(w_max <= MAX_GEWICHT)
|
||||
for sektor, maske in sektormasken().items():
|
||||
obere.append(maske @ w_max <= SEKTORGRENZEN[sektor])
|
||||
cp.Problem(cp.Maximize(mu @ w_max), obere).solve()
|
||||
hoechste = float(mu @ w_max.value)
|
||||
|
||||
# Untere Grenze: die Rendite des Minimum-Varianz-Portfolios
|
||||
ziel_rendite.value = 0.0
|
||||
problem.solve()
|
||||
kleinste = float(mu @ w.value)
|
||||
|
||||
punkte = []
|
||||
for ziel in np.linspace(kleinste, hoechste, PUNKTE):
|
||||
ziel_rendite.value = float(ziel)
|
||||
problem.solve()
|
||||
if problem.status not in (cp.OPTIMAL, cp.OPTIMAL_INACCURATE):
|
||||
continue
|
||||
gewichte = np.asarray(w.value).ravel()
|
||||
risiko = float(np.sqrt(gewichte @ kovarianz @ gewichte))
|
||||
punkte.append((risiko, float(mu @ gewichte), gewichte))
|
||||
return punkte
|
||||
|
||||
|
||||
def aufpreis(frei, gebunden) -> tuple[float, float]:
|
||||
"""Groesster senkrechter Abstand: Wo kosten die Regeln am meisten Rendite?
|
||||
|
||||
Verglichen wird bei GLEICHEM Risiko - alles andere waere kein Preis,
|
||||
sondern nur ein anderer Punkt auf der Kurve.
|
||||
"""
|
||||
risiken_frei = np.array([p[0] for p in frei])
|
||||
renditen_frei = np.array([p[1] for p in frei])
|
||||
bester_abstand, bestes_risiko = 0.0, 0.0
|
||||
for risiko, rendite, _ in gebunden:
|
||||
if risiko < risiken_frei.min() or risiko > risiken_frei.max():
|
||||
continue
|
||||
vergleich = float(np.interp(risiko, risiken_frei, renditen_frei))
|
||||
if vergleich - rendite > bester_abstand:
|
||||
bester_abstand, bestes_risiko = vergleich - rendite, risiko
|
||||
return bester_abstand, bestes_risiko
|
||||
|
||||
|
||||
def zeichne_statisch(frei, gebunden, abstand, risiko_stelle) -> None:
|
||||
figur, achse = plt.subplots(figsize=(7.4, 4.6))
|
||||
for punkte, farbe, name, stil in (
|
||||
(frei, INDIGO, "ohne Beschränkungen", "-"),
|
||||
(gebunden, AMBER, "mit Hausregeln (20 % je Titel, Sektorgrenzen)", "-")):
|
||||
x = [p[0] * 100 for p in punkte]
|
||||
y = [p[1] * 100 for p in punkte]
|
||||
achse.plot(x, y, stil, color=farbe, linewidth=2.0, label=name)
|
||||
|
||||
if abstand > 0:
|
||||
oben = float(np.interp(risiko_stelle,
|
||||
[p[0] for p in frei], [p[1] for p in frei]))
|
||||
unten = oben - abstand
|
||||
achse.annotate("", xy=(risiko_stelle * 100, oben * 100),
|
||||
xytext=(risiko_stelle * 100, unten * 100),
|
||||
arrowprops=dict(arrowstyle="<->", color=GRUEN, lw=1.8))
|
||||
text = f"{abstand * 100:.2f} Prozentpunkte".replace(".", ",")
|
||||
achse.annotate(f"{text}\nRendite, bei gleichem Risiko",
|
||||
(risiko_stelle * 100, (oben + unten) / 2 * 100),
|
||||
textcoords="offset points", xytext=(10, 0),
|
||||
fontsize=8.5, color=GRUEN, va="center")
|
||||
|
||||
# Der teuerste Effekt ist nicht der senkrechte Abstand, sondern die Decke:
|
||||
# Die beschraenkte Linie endet frueher. Alles rechts davon ist mit den
|
||||
# Hausregeln ueberhaupt nicht erreichbar - egal, wie viel Risiko man traegt.
|
||||
ende_gebunden, ende_frei = gebunden[-1], frei[-1]
|
||||
achse.scatter([ende_gebunden[0] * 100], [ende_gebunden[1] * 100], s=110,
|
||||
facecolors="none", edgecolors=AMBER, linewidths=2.2, zorder=5)
|
||||
verlust = (ende_frei[1] - ende_gebunden[1]) * 100
|
||||
achse.annotate(f"hier ist Schluss: {ende_gebunden[1] * 100:.2f} % statt "
|
||||
f"{ende_frei[1] * 100:.2f} %\n"
|
||||
f"— {verlust:.2f} Punkte, die kein Risiko mehr kauft".replace(".", ","),
|
||||
(ende_gebunden[0] * 100, ende_gebunden[1] * 100),
|
||||
textcoords="offset points", xytext=(8, -22),
|
||||
fontsize=8.5, color=AMBER)
|
||||
|
||||
achse.set_xlabel("Volatilität $\\sigma$ (% p. a.)")
|
||||
achse.set_ylabel("erwartete Rendite $\\mu$ (% p. a.)")
|
||||
achse.set_title("Was die Hausregeln kosten", fontsize=11)
|
||||
achse.grid(linestyle=":", alpha=0.5)
|
||||
achse.legend(fontsize=8.5, loc="lower right", frameon=False)
|
||||
for rand in ("top", "right"):
|
||||
achse.spines[rand].set_visible(False)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_markowitz_restriktionen.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
def zeichne_interaktiv(frei, gebunden) -> None:
|
||||
figur = go.Figure()
|
||||
|
||||
def beschriftung(punkt):
|
||||
risiko, rendite, gewichte = punkt
|
||||
reihenfolge = np.argsort(-gewichte)[:4]
|
||||
anteile = "<br>".join(
|
||||
f" {TITEL[i]} ({SEKTOR_VON[i]}): {gewichte[i] * 100:.1f} %"
|
||||
for i in reihenfolge if gewichte[i] > 0.005)
|
||||
sektoren = "<br>".join(
|
||||
f" {sektor}: {float(maske @ gewichte) * 100:.1f} %"
|
||||
for sektor, maske in sektormasken().items())
|
||||
return (f"<b>{rendite * 100:.2f} % Rendite</b><br>"
|
||||
f"{risiko * 100:.2f} % Volatilität<br>"
|
||||
f"Sharpe {(rendite - 0.03) / risiko:.2f}<br><br>"
|
||||
f"<b>größte Positionen</b><br>{anteile}<br><br>"
|
||||
f"<b>je Sektor</b><br>{sektoren}<extra></extra>")
|
||||
|
||||
for punkte, farbe, name in ((frei, INDIGO, "ohne Beschränkungen"),
|
||||
(gebunden, AMBER, "mit Hausregeln")):
|
||||
figur.add_trace(go.Scatter(
|
||||
x=[p[0] * 100 for p in punkte], y=[p[1] * 100 for p in punkte],
|
||||
mode="lines+markers", name=name,
|
||||
line=dict(color=farbe, width=2.2), marker=dict(size=6),
|
||||
hovertemplate=[beschriftung(p) for p in punkte]))
|
||||
|
||||
figur.update_layout(
|
||||
title="Effizienzlinie mit und ohne Hausregeln — Gewichte im Hover",
|
||||
xaxis=dict(title="Volatilität σ (% p. a.)"),
|
||||
yaxis=dict(title="erwartete Rendite μ (% p. a.)"),
|
||||
height=460, margin=dict(l=70, r=20, t=55, b=50),
|
||||
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
||||
legend=dict(orientation="h", y=-0.18), hovermode="closest")
|
||||
|
||||
os.makedirs(PLOTLY_VERZ, exist_ok=True)
|
||||
pfad = os.path.join(PLOTLY_VERZ, "kap_markowitz_restriktionen.html")
|
||||
with open(pfad, "w", encoding="utf-8") as datei:
|
||||
datei.write(figur.to_html(full_html=False, include_plotlyjs=False,
|
||||
div_id="kap-markowitz-restriktionen"))
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mu, kovarianz = universum()
|
||||
frei = linie(mu, kovarianz, beschraenkt=False)
|
||||
gebunden = linie(mu, kovarianz, beschraenkt=True)
|
||||
abstand, stelle = aufpreis(frei, gebunden)
|
||||
print(f"frei: {len(frei)} Punkte, Rendite "
|
||||
f"{frei[0][1] * 100:.2f} bis {frei[-1][1] * 100:.2f} %")
|
||||
print(f"gebunden: {len(gebunden)} Punkte, Rendite "
|
||||
f"{gebunden[0][1] * 100:.2f} bis {gebunden[-1][1] * 100:.2f} %")
|
||||
print(f"groesster Abstand: {abstand * 100:.2f} Prozentpunkte "
|
||||
f"bei sigma = {stelle * 100:.2f} %")
|
||||
zeichne_statisch(frei, gebunden, abstand, stelle)
|
||||
zeichne_interaktiv(frei, gebunden)
|
||||
170
bilder_04/erzeuge_kap06_gantt.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_kap06_gantt.py
|
||||
"""
|
||||
Erzeugt das Gantt-Diagramm zum Job-Shop-Beispiel aus Kapitel 6 in zwei Fassungen:
|
||||
|
||||
bilder_04/kap06_jobshop_gantt.svg statisch, fuer das PDF
|
||||
bilder_04/kap06_jobshop_gantt.png statisch, Rasterfassung
|
||||
bilder_04/plotly/kap06_jobshop_gantt.html interaktiv, fuer die Website
|
||||
|
||||
Die Daten sind identisch mit JobShop_Intervalle.py; geloest wird hier erneut,
|
||||
damit Diagramm und Buchtext nicht auseinanderlaufen koennen.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_kap06_gantt.py
|
||||
|
||||
Benoetigt: ortools, matplotlib, plotly
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg") # kein Fenster, auch auf Servern
|
||||
import matplotlib.pyplot as plt
|
||||
import plotly.graph_objects as go
|
||||
from ortools.sat.python import cp_model
|
||||
|
||||
# Identisch zu JobShop_Intervalle.py
|
||||
AUFTRAEGE = [
|
||||
[(0, 3), (1, 2), (2, 2)], # Auftrag 0
|
||||
[(0, 2), (2, 1), (1, 4)], # Auftrag 1
|
||||
[(1, 4), (2, 3)], # Auftrag 2
|
||||
]
|
||||
MASCHINENNAMEN = ["Fraese", "Dreherei", "Lackiererei"]
|
||||
|
||||
# Buchpalette (Indigo, Cyan, Amber) - dieselben Farben wie im uebrigen Werk
|
||||
FARBEN = ["#4338ca", "#0891b2", "#b45309"]
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
PLOTLY_VERZ = os.path.join(BILDER, "plotly")
|
||||
|
||||
|
||||
def loese() -> tuple[list[tuple[int, int, int, int]], int]:
|
||||
"""Liefert (Auftrag, Maschine, Beginn, Dauer) je Arbeitsgang und den Makespan."""
|
||||
anzahl_maschinen = 1 + max(m for auftrag in AUFTRAEGE for m, _ in auftrag)
|
||||
horizont = sum(dauer for auftrag in AUFTRAEGE for _, dauer in auftrag)
|
||||
|
||||
modell = cp_model.CpModel()
|
||||
intervalle: dict[int, list] = {m: [] for m in range(anzahl_maschinen)}
|
||||
beginn_var: dict[tuple[int, int], object] = {}
|
||||
ende_var: dict[tuple[int, int], object] = {}
|
||||
|
||||
for a, auftrag in enumerate(AUFTRAEGE):
|
||||
for g, (maschine, dauer) in enumerate(auftrag):
|
||||
beginn = modell.NewIntVar(0, horizont, f"b_{a}_{g}")
|
||||
ende = modell.NewIntVar(0, horizont, f"e_{a}_{g}")
|
||||
intervall = modell.NewIntervalVar(beginn, dauer, ende, f"i_{a}_{g}")
|
||||
intervalle[maschine].append(intervall)
|
||||
beginn_var[a, g] = beginn
|
||||
ende_var[a, g] = ende
|
||||
if g > 0: # Arbeitsgaenge in ihrer Reihenfolge
|
||||
modell.Add(beginn >= ende_var[a, g - 1])
|
||||
|
||||
for maschine in range(anzahl_maschinen):
|
||||
modell.AddNoOverlap(intervalle[maschine])
|
||||
|
||||
makespan = modell.NewIntVar(0, horizont, "makespan")
|
||||
modell.AddMaxEquality(makespan, [ende_var[a, len(auftrag) - 1]
|
||||
for a, auftrag in enumerate(AUFTRAEGE)])
|
||||
modell.Minimize(makespan)
|
||||
|
||||
loeser = cp_model.CpSolver()
|
||||
# Dieselben Parameter wie in JobShop_Intervalle.py - sonst zeigt das
|
||||
# Diagramm einen anderen (gleich guten) Plan als der Buchtext.
|
||||
loeser.parameters.num_workers = 1
|
||||
loeser.parameters.random_seed = 1
|
||||
status = loeser.Solve(modell)
|
||||
if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
|
||||
raise RuntimeError(f"Kein Plan gefunden: {loeser.StatusName(status)}")
|
||||
|
||||
plan = [(a, maschine, loeser.Value(beginn_var[a, g]), dauer)
|
||||
for a, auftrag in enumerate(AUFTRAEGE)
|
||||
for g, (maschine, dauer) in enumerate(auftrag)]
|
||||
return plan, loeser.Value(makespan)
|
||||
|
||||
|
||||
def zeichne_statisch(plan, makespan: int) -> None:
|
||||
"""Matplotlib-Fassung fuer das PDF."""
|
||||
figur, achse = plt.subplots(figsize=(9.0, 3.2))
|
||||
for auftrag, maschine, beginn, dauer in plan:
|
||||
achse.barh(y=maschine, width=dauer, left=beginn, height=0.55,
|
||||
color=FARBEN[auftrag % len(FARBEN)],
|
||||
edgecolor="white", linewidth=1.2)
|
||||
achse.text(beginn + dauer / 2, maschine, f"A{auftrag}",
|
||||
ha="center", va="center", color="white",
|
||||
fontsize=10, fontweight="bold")
|
||||
|
||||
achse.set_yticks(range(len(MASCHINENNAMEN)))
|
||||
achse.set_yticklabels(MASCHINENNAMEN)
|
||||
achse.invert_yaxis()
|
||||
achse.set_xlabel("Zeiteinheiten")
|
||||
achse.set_xlim(0, makespan)
|
||||
achse.set_xticks(range(makespan + 1))
|
||||
achse.set_title(f"Job-Shop-Plan, Makespan = {makespan}", fontsize=11)
|
||||
achse.grid(axis="x", linestyle=":", alpha=0.5)
|
||||
for rand in ("top", "right", "left"):
|
||||
achse.spines[rand].set_visible(False)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap06_jobshop_gantt.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
def zeichne_interaktiv(plan, makespan: int) -> None:
|
||||
"""Plotly-Fassung fuer die Website.
|
||||
|
||||
include_plotlyjs=False: Die Bibliothek wird vom Build einmal je Seite
|
||||
eingebunden (assets/plotly.min.js), nicht in jede Figur kopiert.
|
||||
"""
|
||||
figur = go.Figure()
|
||||
schon_beschriftet = set()
|
||||
for auftrag, maschine, beginn, dauer in plan:
|
||||
figur.add_trace(go.Bar(
|
||||
x=[dauer], y=[MASCHINENNAMEN[maschine]], base=[beginn],
|
||||
orientation="h", name=f"Auftrag {auftrag}",
|
||||
legendgroup=f"A{auftrag}",
|
||||
showlegend=auftrag not in schon_beschriftet,
|
||||
marker_color=FARBEN[auftrag % len(FARBEN)],
|
||||
marker_line=dict(color="white", width=1.5),
|
||||
hovertemplate=(f"<b>Auftrag {auftrag}</b><br>"
|
||||
f"{MASCHINENNAMEN[maschine]}<br>"
|
||||
f"von {beginn} bis {beginn + dauer} "
|
||||
f"({dauer} Zeiteinheiten)<extra></extra>"),
|
||||
))
|
||||
schon_beschriftet.add(auftrag)
|
||||
|
||||
figur.update_layout(
|
||||
barmode="overlay",
|
||||
title=f"Job-Shop-Plan, Makespan = {makespan}",
|
||||
xaxis=dict(title="Zeiteinheiten", dtick=1, range=[0, makespan]),
|
||||
yaxis=dict(autorange="reversed"),
|
||||
height=320,
|
||||
margin=dict(l=90, r=20, t=50, b=45),
|
||||
# Transparenter Hintergrund, damit die Figur sich in helles wie
|
||||
# dunkles Seitendesign einfuegt.
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
legend=dict(orientation="h", y=-0.25),
|
||||
)
|
||||
|
||||
os.makedirs(PLOTLY_VERZ, exist_ok=True)
|
||||
pfad = os.path.join(PLOTLY_VERZ, "kap06_jobshop_gantt.html")
|
||||
with open(pfad, "w", encoding="utf-8") as datei:
|
||||
datei.write(figur.to_html(full_html=False, include_plotlyjs=False,
|
||||
div_id="kap06-jobshop-gantt"))
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
plan, makespan = loese()
|
||||
print(f"Makespan: {makespan}")
|
||||
zeichne_statisch(plan, makespan)
|
||||
zeichne_interaktiv(plan, makespan)
|
||||
224
bilder_04/erzeuge_kkt.py
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_kkt.py
|
||||
"""
|
||||
Erzeugt das KKT-Diagramm zum QP/NLP-Kapitel:
|
||||
|
||||
bilder_04/kap_qp_kkt.svg / .png
|
||||
|
||||
Zwei Bilder nebeneinander:
|
||||
|
||||
Links - die Handrechnung des Kapitels, eindimensional:
|
||||
min (x-5)^2 u.d.N. x - 3 <= 0 -> x* = 3, lambda* = 4
|
||||
Sichtbar wird, was lambda BEDEUTET: Es ist die Steigung von f am
|
||||
Rand, also der Preis dafuer, dass die Grenze nicht weiter darf.
|
||||
Rechts - dieselbe Aussage in zwei Dimensionen: Am Optimum zeigen der
|
||||
Zielgradient und der mit lambda gewichtete Restriktionsgradient
|
||||
GENAU GEGENEINANDER und heben sich auf.
|
||||
|
||||
Warum neu: Im Vorgaengerbild war der Zielpfeil mit "-grad f" beschriftet und
|
||||
zeigte entgegen lambda*grad g. Das ist die Konstellation einer MAXIMIERUNG. Das
|
||||
Kapitel formuliert KKT aber fuer die Minimierung - dort gilt
|
||||
grad f + lambda*grad g = 0, also zeigen grad f (ohne Minus) und lambda*grad g
|
||||
gegeneinander. Eine Beschriftung, ein Vorzeichen, und die Aussage kippt.
|
||||
|
||||
Alle Werte werden gerechnet, nicht eingetragen: Das Optimum kommt aus
|
||||
scipy.optimize, die Gradienten aus der Ableitung, und das Skript prueft alle
|
||||
vier KKT-Bedingungen numerisch, bevor es zeichnet.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_kkt.py
|
||||
|
||||
Benoetigt: numpy, scipy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy.optimize import minimize
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRUEN, ROT = (
|
||||
"#4338ca", "#0891b2", "#b45309", "#059669", "#be123c")
|
||||
|
||||
# --- Eindimensional: die Handrechnung des Kapitels --------------------------
|
||||
ZIEL_1D = 5.0 # unbeschraenktes Minimum von (x-5)^2
|
||||
GRENZE_1D = 3.0 # x <= 3
|
||||
|
||||
|
||||
# --- Zweidimensional: dieselbe Struktur, ein Ziel mehr ----------------------
|
||||
ZENTRUM = np.array([3.5, 3.5]) # unbeschraenktes Minimum
|
||||
BUDGET = 4.0 # x1 + x2 <= 4
|
||||
|
||||
|
||||
def f(x):
|
||||
return float(np.sum((np.asarray(x) - ZENTRUM) ** 2))
|
||||
|
||||
|
||||
def grad_f(x):
|
||||
return 2.0 * (np.asarray(x) - ZENTRUM)
|
||||
|
||||
|
||||
def g(x):
|
||||
return float(np.sum(x) - BUDGET)
|
||||
|
||||
|
||||
GRAD_G = np.array([1.0, 1.0])
|
||||
|
||||
|
||||
def loese_2d():
|
||||
ergebnis = minimize(f, x0=np.zeros(2), method="SLSQP",
|
||||
constraints=[{"type": "ineq", "fun": lambda x: -g(x)}])
|
||||
if not ergebnis.success:
|
||||
raise SystemExit(f"SLSQP gescheitert: {ergebnis.message}")
|
||||
x = np.asarray(ergebnis.x)
|
||||
# lambda aus der Stationaritaet: grad f + lambda * grad g = 0
|
||||
lam = float(-(grad_f(x) @ GRAD_G) / (GRAD_G @ GRAD_G))
|
||||
return x, lam
|
||||
|
||||
|
||||
def pruefe_kkt(x, lam, toleranz=1e-5) -> None:
|
||||
"""Alle vier Bedingungen, bevor gezeichnet wird - nicht danach."""
|
||||
stationaritaet = grad_f(x) + lam * GRAD_G
|
||||
pruefungen = {
|
||||
"Stationarität grad f + lambda*grad g = 0":
|
||||
float(np.linalg.norm(stationaritaet)) < toleranz,
|
||||
"primale Zulässigkeit g(x) <= 0": g(x) < toleranz,
|
||||
"duale Zulässigkeit lambda >= 0": lam > -toleranz,
|
||||
"komplementärer Schlupf lambda*g(x) = 0": abs(lam * g(x)) < toleranz,
|
||||
}
|
||||
for name, erfuellt in pruefungen.items():
|
||||
print(f" {'OK ' if erfuellt else 'NEIN'} {name}")
|
||||
if not all(pruefungen.values()):
|
||||
raise SystemExit("FEHLER: KKT nicht erfuellt - es wird nichts gezeichnet.")
|
||||
|
||||
|
||||
def zahl(wert: float) -> str:
|
||||
text = f"{wert:.2f}".rstrip("0").rstrip(".")
|
||||
return text.replace(".", ",")
|
||||
|
||||
|
||||
def zeichne_1d(achse, lam_1d: float) -> None:
|
||||
x = np.linspace(0.5, 6.5, 400)
|
||||
achse.plot(x, (x - ZIEL_1D) ** 2, color=INDIGO, linewidth=2.0,
|
||||
label="$f(x) = (x-5)^2$")
|
||||
achse.axvspan(GRENZE_1D, 6.5, color=ROT, alpha=0.07)
|
||||
achse.axvline(GRENZE_1D, color=ROT, linewidth=1.8, linestyle="--")
|
||||
achse.annotate("unzulässig:\n$x > 3$", (GRENZE_1D, 20),
|
||||
textcoords="offset points", xytext=(10, 0),
|
||||
fontsize=8.5, color=ROT, va="center")
|
||||
|
||||
achse.scatter([ZIEL_1D], [0.0], s=90, facecolors="none",
|
||||
edgecolors="#94a3b8", linewidths=2.0, zorder=4)
|
||||
achse.annotate("freies Minimum $x=5$\n— nicht erlaubt", (ZIEL_1D, 0.0),
|
||||
textcoords="offset points", xytext=(10, 16),
|
||||
ha="left", fontsize=8.5, color="#64748b")
|
||||
|
||||
wert = (GRENZE_1D - ZIEL_1D) ** 2
|
||||
achse.scatter([GRENZE_1D], [wert], s=130, color=GRUEN, zorder=5)
|
||||
achse.annotate(f"$x^*=3$, $f=4$", (GRENZE_1D, wert),
|
||||
textcoords="offset points", xytext=(-10, 12),
|
||||
ha="right", fontsize=9, color=GRUEN, fontweight="bold")
|
||||
|
||||
# Die Tangente am Rand: ihre Steigung ist -lambda*
|
||||
steigung = 2 * (GRENZE_1D - ZIEL_1D)
|
||||
nahe = np.linspace(GRENZE_1D - 1.2, GRENZE_1D + 0.8, 20)
|
||||
achse.plot(nahe, wert + steigung * (nahe - GRENZE_1D), color=AMBER,
|
||||
linewidth=1.6, linestyle="-",
|
||||
label=f"Steigung $-\\lambda^* = {zahl(steigung)}$")
|
||||
|
||||
achse.set_xlabel("$x$")
|
||||
achse.set_ylabel("$f(x)$")
|
||||
achse.set_title("Die Handrechnung: $\\lambda^*$ ist die Steigung am Rand",
|
||||
fontsize=10.5)
|
||||
achse.set_ylim(-1.5, 22)
|
||||
achse.grid(linestyle=":", alpha=0.45)
|
||||
achse.legend(fontsize=8, loc="upper left", frameon=False)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
|
||||
|
||||
def zeichne_2d(achse, x_stern, lam) -> None:
|
||||
gitter = np.linspace(-0.2, 5.4, 300)
|
||||
X1, X2 = np.meshgrid(gitter, gitter)
|
||||
werte = (X1 - ZENTRUM[0]) ** 2 + (X2 - ZENTRUM[1]) ** 2
|
||||
achse.contour(X1, X2, werte, levels=[0.5, 2, 4.5, 8, 12.5, 18],
|
||||
colors=[INDIGO], linewidths=0.8, alpha=0.55)
|
||||
|
||||
rand = np.linspace(-0.2, 5.4, 10)
|
||||
achse.plot(rand, BUDGET - rand, color=ROT, linewidth=2.0,
|
||||
label="$g(x)=x_1+x_2-4=0$")
|
||||
achse.fill_between(rand, -0.2, np.minimum(BUDGET - rand, 5.4),
|
||||
color=CYAN, alpha=0.08)
|
||||
|
||||
achse.scatter(*ZENTRUM, s=90, facecolors="none", edgecolors="#94a3b8",
|
||||
linewidths=2.0, zorder=4)
|
||||
achse.annotate("freies Minimum\n— unzulässig", ZENTRUM,
|
||||
textcoords="offset points", xytext=(12, -8),
|
||||
va="top", fontsize=8.5, color="#64748b")
|
||||
|
||||
achse.scatter(*x_stern, s=140, color=GRUEN, zorder=6)
|
||||
achse.annotate(f"$x^* = ({zahl(x_stern[0])}; {zahl(x_stern[1])})$",
|
||||
x_stern, textcoords="offset points", xytext=(-12, -20),
|
||||
ha="right", fontsize=9, color=GRUEN, fontweight="bold")
|
||||
|
||||
zielgradient = grad_f(x_stern)
|
||||
achse.annotate("", xy=x_stern + zielgradient * 0.45, xytext=x_stern,
|
||||
arrowprops=dict(arrowstyle="-|>", color=INDIGO, lw=2.4))
|
||||
achse.annotate("$\\nabla f$ — zeigt bergauf,\nweg vom freien Minimum",
|
||||
x_stern + zielgradient * 0.45,
|
||||
textcoords="offset points", xytext=(-8, -8),
|
||||
ha="right", va="top", fontsize=8.5, color=INDIGO)
|
||||
|
||||
restriktionsgradient = lam * GRAD_G
|
||||
achse.annotate("", xy=x_stern + restriktionsgradient * 0.45, xytext=x_stern,
|
||||
arrowprops=dict(arrowstyle="-|>", color=AMBER, lw=2.4))
|
||||
achse.annotate(f"$\\lambda^*\\nabla g$ mit $\\lambda^* = {zahl(lam)}$",
|
||||
x_stern + restriktionsgradient * 0.45,
|
||||
textcoords="offset points", xytext=(-10, 8),
|
||||
ha="right", fontsize=8.5, color=AMBER)
|
||||
|
||||
achse.set_xlim(-0.2, 5.4)
|
||||
achse.set_ylim(-0.2, 5.4)
|
||||
achse.set_xlabel("$x_1$")
|
||||
achse.set_ylabel("$x_2$")
|
||||
achse.set_title("Am Optimum heben sich beide Gradienten auf:\n"
|
||||
"$\\nabla f + \\lambda^*\\nabla g = 0$", fontsize=10.5)
|
||||
achse.grid(linestyle=":", alpha=0.45)
|
||||
achse.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
x_stern, lam = loese_2d()
|
||||
print(f"2D: x* = ({x_stern[0]:.4f}; {x_stern[1]:.4f}), lambda* = {lam:.4f}")
|
||||
pruefe_kkt(x_stern, lam)
|
||||
|
||||
lam_1d = -2 * (GRENZE_1D - ZIEL_1D)
|
||||
print(f"1D: x* = {GRENZE_1D:g}, lambda* = {lam_1d:g} "
|
||||
f"(Handrechnung: 3 und 4)")
|
||||
if abs(lam_1d - 4.0) > 1e-9:
|
||||
raise SystemExit("FEHLER: 1D-Lambda weicht von der Handrechnung ab.")
|
||||
|
||||
figur, (links, rechts) = plt.subplots(1, 2, figsize=(11.0, 4.8))
|
||||
zeichne_1d(links, lam_1d)
|
||||
zeichne_2d(rechts, x_stern, lam)
|
||||
figur.tight_layout()
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_qp_kkt.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
204
bilder_04/erzeuge_kondition.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_kondition.py
|
||||
"""
|
||||
Erzeugt das Konditionszahl-Diagramm zum Kapitel Fundament:
|
||||
|
||||
bilder_04/kap_fundament_kondition.svg statisch, fuer das PDF
|
||||
bilder_04/kap_fundament_kondition.png Rasterfassung
|
||||
|
||||
Zwei Aussagen, beide gerechnet und nicht behauptet:
|
||||
|
||||
Links - kappa(A) ist keine grobe Faustregel, sondern eine SCHARFE obere
|
||||
Schranke. Die gemessene groesste Fehlerverstaerkung liegt ueber
|
||||
zwoelf Groessenordnungen hinweg praktisch auf der Geraden y = kappa;
|
||||
der Median darunter. Wer also mit kappa = 1e8 rechnet, verliert im
|
||||
schlimmsten Fall genau acht Stellen - nicht ungefaehr, sondern
|
||||
nachweisbar.
|
||||
Rechts - Was die Ruiz-Equilibrierung an einem Modell ausrichtet, in dem
|
||||
Euro-Betraege und Tonnen-Angaben in derselben Matrix stehen.
|
||||
|
||||
Verfahren und Instanz sind wortgleich zu Skalierung_Kondition.py, und
|
||||
gerechnet wird hier erneut - so koennen Diagramm und Buchtext nicht
|
||||
auseinanderlaufen.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_kondition.py
|
||||
|
||||
Benoetigt: numpy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRUEN = "#4338ca", "#0891b2", "#b45309", "#059669"
|
||||
|
||||
STOERUNG = 1e-10 # relative Stoerung der rechten Seite
|
||||
|
||||
GUT = np.array([[2.0, 1.0], [1.0, 3.0]])
|
||||
SCHLECHT = np.array([[1.0, 1.0], [1.0, 1.0 + 1e-8]])
|
||||
|
||||
|
||||
# --- Messung: wortgleich zu Skalierung_Kondition.py -------------------------
|
||||
|
||||
def fehlerverstaerkung(A: np.ndarray, rng: np.random.Generator,
|
||||
versuche: int = 200) -> tuple[float, float]:
|
||||
"""Stoert b relativ um STOERUNG und misst den Faktor, um den der
|
||||
Loesungsfehler groesser ausfaellt. Liefert (Median, Maximum)."""
|
||||
x_wahr = np.ones(A.shape[1])
|
||||
b = A @ x_wahr
|
||||
faktoren = []
|
||||
for _ in range(versuche):
|
||||
richtung = rng.normal(size=b.size)
|
||||
richtung /= np.linalg.norm(richtung)
|
||||
b_gestoert = b + STOERUNG * np.linalg.norm(b) * richtung
|
||||
x_gestoert = np.linalg.solve(A, b_gestoert)
|
||||
rel_x = np.linalg.norm(x_gestoert - x_wahr) / np.linalg.norm(x_wahr)
|
||||
faktoren.append(rel_x / STOERUNG)
|
||||
return float(np.median(faktoren)), float(np.max(faktoren))
|
||||
|
||||
|
||||
def ruiz_equilibrierung(A: np.ndarray, durchlaeufe: int = 20) -> np.ndarray:
|
||||
"""Zeilen- und Spaltenskalierung, bis alle Maxima nahe 1 liegen."""
|
||||
A_s = A.astype(float).copy()
|
||||
for _ in range(durchlaeufe):
|
||||
zeilen_max = np.abs(A_s).max(axis=1)
|
||||
zeilen_max[zeilen_max == 0] = 1.0
|
||||
A_s = (1.0 / np.sqrt(zeilen_max))[:, None] * A_s
|
||||
spalten_max = np.abs(A_s).max(axis=0)
|
||||
spalten_max[spalten_max == 0] = 1.0
|
||||
A_s = A_s * (1.0 / np.sqrt(spalten_max))[None, :]
|
||||
return A_s
|
||||
|
||||
|
||||
def mischmodell() -> np.ndarray:
|
||||
"""Das Modell mit den vier unvertraeglichen Einheiten.
|
||||
|
||||
Skalierung_Kondition.py zieht diese Matrix aus DEMSELBEN Generator, aus dem
|
||||
zuvor schon die Stoerungen von Experiment 1 kamen. Damit hier bitgleich
|
||||
dieselbe Matrix entsteht - und das Diagramm dieselbe Konditionszahl zeigt
|
||||
wie der abgedruckte Programmlauf -, wird die Ziehungsreihenfolge exakt
|
||||
nachgespielt: erst 2 x 200 Stoerungen, dann die Grundmatrix.
|
||||
"""
|
||||
rng = np.random.default_rng(42)
|
||||
for A in (GUT, SCHLECHT):
|
||||
fehlerverstaerkung(A, rng)
|
||||
grundmatrix = rng.uniform(0.5, 2.0, size=(4, 4))
|
||||
einheiten = np.array([1e7, 1e-3, 1e5, 1e-2])
|
||||
return grundmatrix * einheiten[:, None]
|
||||
|
||||
|
||||
def messreihe() -> tuple[list[float], list[float], list[float]]:
|
||||
"""Eine Familie fast paralleler Zeilen: A = [[1, 1], [1, 1 + eps]].
|
||||
|
||||
Mit fallendem eps waechst kappa(A) um zwoelf Groessenordnungen, ohne dass
|
||||
sich sonst irgendetwas am Modell aendert. Genau das macht die Kurve
|
||||
aussagekraeftig: Der einzige Unterschied ist die Kondition.
|
||||
"""
|
||||
# Eigener Generator, damit die Messreihe den nachgespielten Strom in
|
||||
# mischmodell() nicht stoert.
|
||||
rng = np.random.default_rng(7)
|
||||
kappas, mediane, maxima = [], [], []
|
||||
for exponent in np.arange(-1.0, -12.5, -0.5):
|
||||
eps = 10.0 ** exponent
|
||||
A = np.array([[1.0, 1.0], [1.0, 1.0 + eps]])
|
||||
median, maximum = fehlerverstaerkung(A, rng)
|
||||
kappas.append(float(np.linalg.cond(A)))
|
||||
mediane.append(median)
|
||||
maxima.append(maximum)
|
||||
return kappas, mediane, maxima
|
||||
|
||||
|
||||
# --- Zeichnen ---------------------------------------------------------------
|
||||
|
||||
def zeichne(kappas, mediane, maxima, kappa_vorher, kappa_nachher) -> None:
|
||||
figur, (links, rechts) = plt.subplots(
|
||||
1, 2, figsize=(10.4, 4.3), gridspec_kw={"width_ratios": [1.75, 1]})
|
||||
|
||||
# --- links: Verstaerkung gegen kappa ---
|
||||
# Die Gerade reicht bewusst ueber die Messpunkte hinaus: Innerhalb des
|
||||
# Messbereichs liegt sie exakt unter der Messkurve - und genau das ist die
|
||||
# Aussage. Sichtbar bleibt sie dadurch an den Enden.
|
||||
grenze = np.array([min(kappas) / 30, max(kappas) * 30])
|
||||
links.plot(grenze, grenze, color="#94a3b8", linewidth=3.0, linestyle="-",
|
||||
alpha=0.55, zorder=1,
|
||||
label="theoretische Schranke $y=\\kappa(A)$")
|
||||
links.plot(kappas, maxima, "o-", color=INDIGO, markersize=4.5, linewidth=1.4,
|
||||
zorder=3, label="gemessen: größte Verstärkung")
|
||||
links.plot(kappas, mediane, "s-", color=CYAN, markersize=4.0, linewidth=1.4,
|
||||
zorder=2, label="gemessen: Median")
|
||||
|
||||
marken_rng = np.random.default_rng(7)
|
||||
for A, name, versatz in ((GUT, "gut konditioniert", (14, -4)),
|
||||
(SCHLECHT, "fast parallele Zeilen", (-12, 14))):
|
||||
_, maximum = fehlerverstaerkung(A, marken_rng)
|
||||
k = float(np.linalg.cond(A))
|
||||
links.scatter([k], [maximum], s=95, facecolors="none", edgecolors=AMBER,
|
||||
linewidths=2.0, zorder=4)
|
||||
links.annotate(name, (k, maximum), textcoords="offset points",
|
||||
xytext=versatz, fontsize=8, color=AMBER,
|
||||
ha="right" if versatz[0] < 0 else "left")
|
||||
|
||||
links.set_xscale("log")
|
||||
links.set_yscale("log")
|
||||
links.set_xlabel("Konditionszahl $\\kappa(A)$")
|
||||
links.set_ylabel("Fehlerverstärkung (Faktor)")
|
||||
links.set_title("$\\kappa(A)$ ist eine scharfe Schranke, keine Faustregel",
|
||||
fontsize=10.5)
|
||||
links.grid(which="both", linestyle=":", alpha=0.45)
|
||||
links.legend(fontsize=8, loc="upper left", frameon=False)
|
||||
links.annotate("Die Messung liegt auf der Schranke:\n"
|
||||
"$\\kappa$ wird im schlimmsten Fall\nvollständig ausgeschöpft.",
|
||||
xy=(0.97, 0.06), xycoords="axes fraction", fontsize=8,
|
||||
ha="right", va="bottom", color="#475569")
|
||||
for rand in ("top", "right"):
|
||||
links.spines[rand].set_visible(False)
|
||||
|
||||
# --- rechts: was Ruiz ausrichtet ---
|
||||
balken = rechts.bar(["vorher", "nach Ruiz"], [kappa_vorher, kappa_nachher],
|
||||
color=[AMBER, GRUEN], width=0.55)
|
||||
rechts.set_yscale("log")
|
||||
rechts.set_ylim(1, kappa_vorher * 40)
|
||||
for stab, wert in zip(balken, (kappa_vorher, kappa_nachher)):
|
||||
rechts.annotate(f"{wert:.2e}", (stab.get_x() + stab.get_width() / 2, wert),
|
||||
textcoords="offset points", xytext=(0, 6),
|
||||
ha="center", fontsize=9, fontweight="bold")
|
||||
stellen = int(round(np.log10(kappa_vorher / kappa_nachher)))
|
||||
rechts.set_title(f"Ruiz-Equilibrierung:\n{stellen} Größenordnungen besser",
|
||||
fontsize=10.5)
|
||||
rechts.set_ylabel("$\\kappa(A)$")
|
||||
rechts.grid(axis="y", which="both", linestyle=":", alpha=0.45)
|
||||
for rand in ("top", "right"):
|
||||
rechts.spines[rand].set_visible(False)
|
||||
|
||||
figur.tight_layout()
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_fundament_kondition.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
kappas, mediane, maxima = messreihe()
|
||||
modell = mischmodell()
|
||||
kappa_vorher = float(np.linalg.cond(modell))
|
||||
kappa_nachher = float(np.linalg.cond(ruiz_equilibrierung(modell)))
|
||||
print(f"Messreihe: {len(kappas)} Matrizen, kappa von {min(kappas):.2e} "
|
||||
f"bis {max(kappas):.2e}")
|
||||
print(f"Ruiz: kappa {kappa_vorher:.2e} -> {kappa_nachher:.2e}")
|
||||
zeichne(kappas, mediane, maxima, kappa_vorher, kappa_nachher)
|
||||
299
bilder_04/erzeuge_metaheuristik_landschaft.py
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_metaheuristik_landschaft.py
|
||||
"""
|
||||
Erzeugt das Suchverlauf-Diagramm zum Kapitel Metaheuristiken:
|
||||
|
||||
bilder_04/kap_metaheuristiken_landschaft.svg statisch, fuer das PDF
|
||||
bilder_04/kap_metaheuristiken_landschaft.png Rasterfassung
|
||||
bilder_04/plotly/kap_metaheuristiken_verlauf.html interaktiv, fuer die Website
|
||||
|
||||
Eine Suchlandschaft laesst sich bei 200 Auftraegen nicht zeichnen - der Raum
|
||||
hat 200! Punkte. Zeichnen laesst sich aber, wie die Suche sie DURCHLAEUFT, und
|
||||
das zeigt dasselbe:
|
||||
|
||||
Links - der Zielwert waehrend der Suche. Bergsteigen faellt monoton und
|
||||
bleibt liegen. Annealing bei T=1 faellt fast genauso, mit winzigen
|
||||
Ausschlaegen nach oben. Bei T=8 irrt die Suche weit ueber den
|
||||
Startwert hinaus und findet nicht zurueck - die Temperatur ist kein
|
||||
Beiwerk.
|
||||
Rechts - das Ergebnis ueber die Starttemperatur. Es gibt ein Optimum, es ist
|
||||
schmal, und jenseits davon wird die Metaheuristik schlechter als die
|
||||
Faustregel, mit der sie gestartet ist.
|
||||
|
||||
Instanz, Zug und Abkuehlplan sind wortgleich zu Simulated_Annealing.py, und
|
||||
gerechnet wird hier erneut - so koennen Diagramm und Buchtext nicht
|
||||
auseinanderlaufen. Der einzige Unterschied: Die Suche zeichnet ihren Verlauf
|
||||
mit, was das Buchprogramm nicht braucht.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis, dauert rund eine Minute):
|
||||
python3 bilder_04/erzeuge_metaheuristik_landschaft.py
|
||||
|
||||
Die interaktive Fassung traegt im Hover die Groesse, die das statische Bild
|
||||
nicht zeigen kann: die Temperatur an dieser Stelle des Abkuehlplans - und was
|
||||
sie konkret bedeutet, naemlich mit welcher Wahrscheinlichkeit dort ein Zug
|
||||
typischer Groesse (Median 70 Minuten, im Kapitel gemessen) angenommen wuerde.
|
||||
Damit wird an der Kurve ablesbar, warum T0 = 8 so lange braucht, um
|
||||
zurueckzufinden.
|
||||
|
||||
Benoetigt: numpy, matplotlib, plotly
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
PLOTLY_VERZ = os.path.join(BILDER, "plotly")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRAU = "#4338ca", "#0891b2", "#b45309", "#94a3b8"
|
||||
|
||||
N_AUFTRAEGE = 200
|
||||
ZUEGE = 400_000
|
||||
SAAT = 11
|
||||
STUETZSTELLEN = 800 # so viele Punkte je Verlaufskurve
|
||||
MEDIAN_ZUG = 70 # typische Zuggroesse in Minuten (im Kapitel gemessen)
|
||||
|
||||
|
||||
# --- Instanz und Zug: wortgleich zu Simulated_Annealing.py ------------------
|
||||
|
||||
def erzeuge_ruestmatrix(n: int, saat: int = SAAT) -> np.ndarray:
|
||||
rng = np.random.default_rng(saat)
|
||||
familien = max(6, n // 10)
|
||||
zwischen = rng.integers(8, 60, (familien, familien))
|
||||
np.fill_diagonal(zwischen, 2)
|
||||
familie = rng.integers(0, familien, n)
|
||||
farbe = rng.integers(0, 10, n)
|
||||
matrix = np.zeros((n, n), dtype=np.int64)
|
||||
for i in range(n):
|
||||
for j in range(n):
|
||||
if i != j:
|
||||
matrix[i, j] = (zwischen[familie[i], familie[j]]
|
||||
+ max(0, farbe[i] - farbe[j]) * 3)
|
||||
return matrix
|
||||
|
||||
|
||||
def gesamtruestzeit(reihe: list[int], matrix: np.ndarray) -> int:
|
||||
return int(sum(matrix[reihe[k], reihe[k + 1]] for k in range(len(reihe) - 1)))
|
||||
|
||||
|
||||
def faustregel(matrix: np.ndarray) -> list[int]:
|
||||
n = len(matrix)
|
||||
offen = set(range(1, n))
|
||||
reihe = [0]
|
||||
while offen:
|
||||
naechster = min(offen, key=lambda j: matrix[reihe[-1], j])
|
||||
reihe.append(naechster)
|
||||
offen.discard(naechster)
|
||||
return reihe
|
||||
|
||||
|
||||
def delta_verschieben(reihe: list[int], matrix: np.ndarray, von: int, nach: int):
|
||||
n = len(reihe)
|
||||
if von == nach or nach == von + 1:
|
||||
return None
|
||||
heraus = 0
|
||||
if von > 0:
|
||||
heraus += matrix[reihe[von - 1], reihe[von]]
|
||||
if von < n - 1:
|
||||
heraus += matrix[reihe[von], reihe[von + 1]]
|
||||
if 0 < von < n - 1:
|
||||
heraus -= matrix[reihe[von - 1], reihe[von + 1]]
|
||||
rest = reihe[:von] + reihe[von + 1:]
|
||||
stelle = nach if nach < von else nach - 1
|
||||
hinein = 0
|
||||
if stelle > 0:
|
||||
hinein += matrix[rest[stelle - 1], reihe[von]]
|
||||
if stelle < len(rest):
|
||||
hinein += matrix[reihe[von], rest[stelle]]
|
||||
if 0 < stelle < len(rest):
|
||||
hinein -= matrix[rest[stelle - 1], rest[stelle]]
|
||||
return int(hinein - heraus), rest, stelle
|
||||
|
||||
|
||||
# --- Die Suche, um die Verlaufsaufzeichnung ergaenzt ------------------------
|
||||
|
||||
def suche_mit_verlauf(matrix: np.ndarray, start_temperatur: float,
|
||||
saat: int = 1, bergsteigen: bool = False,
|
||||
end_temperatur: float = 0.05):
|
||||
"""Wie suche() im Buchprogramm, protokolliert zusaetzlich den Zielwert.
|
||||
|
||||
Aufgezeichnet wird der AKTUELLE Zustand, nicht der beste. Genau darin liegt
|
||||
die Aussage: Der beste Wert faellt bei jedem Verfahren monoton - was die
|
||||
Verfahren unterscheidet, ist, wie weit sich die Suche unterwegs vom Guten
|
||||
entfernen darf.
|
||||
"""
|
||||
rng = np.random.default_rng(saat)
|
||||
n = len(matrix)
|
||||
reihe = faustregel(matrix)
|
||||
kosten = gesamtruestzeit(reihe, matrix)
|
||||
beste_kosten = kosten
|
||||
takt = max(1, ZUEGE // STUETZSTELLEN)
|
||||
x, y, temperaturen = [0], [kosten], [start_temperatur]
|
||||
|
||||
for zug in range(ZUEGE):
|
||||
temperatur = start_temperatur * (end_temperatur / start_temperatur) \
|
||||
** (zug / ZUEGE)
|
||||
von = int(rng.integers(0, n))
|
||||
nach = int(rng.integers(0, n + 1))
|
||||
ergebnis = delta_verschieben(reihe, matrix, von, nach)
|
||||
if ergebnis is not None:
|
||||
aenderung, rest, stelle = ergebnis
|
||||
if aenderung <= 0:
|
||||
annehmen = True
|
||||
else:
|
||||
annehmen = (not bergsteigen
|
||||
and rng.random() < math.exp(-aenderung / temperatur))
|
||||
if annehmen:
|
||||
reihe = rest[:stelle] + [reihe[von]] + rest[stelle:]
|
||||
kosten += aenderung
|
||||
beste_kosten = min(beste_kosten, kosten)
|
||||
if (zug + 1) % takt == 0:
|
||||
x.append(zug + 1)
|
||||
y.append(kosten)
|
||||
temperaturen.append(temperatur)
|
||||
return x, y, beste_kosten, temperaturen
|
||||
|
||||
|
||||
# --- Zeichnen ---------------------------------------------------------------
|
||||
|
||||
def zeichne(verlaeufe, start, temperaturreihe) -> None:
|
||||
figur, (links, rechts) = plt.subplots(
|
||||
1, 2, figsize=(10.6, 4.4), gridspec_kw={"width_ratios": [1.7, 1]})
|
||||
|
||||
links.axhline(start, color=GRAU, linewidth=1.2, linestyle="--", zorder=1)
|
||||
links.annotate(f"Faustregel: {start:,}".replace(",", "."),
|
||||
(ZUEGE, start), textcoords="offset points", xytext=(-4, 6),
|
||||
ha="right", fontsize=8, color="#475569")
|
||||
|
||||
for (name, farbe, breite), (x, y, bestes, _) in verlaeufe:
|
||||
links.plot(x, y, color=farbe, linewidth=breite, alpha=0.9,
|
||||
label=f"{name} → {bestes:,}".replace(",", "."))
|
||||
|
||||
links.set_xlabel("geprüfte Züge")
|
||||
links.set_ylabel("Rüstzeit des aktuellen Plans (Minuten)")
|
||||
links.set_title("Wie die Suche die Landschaft durchläuft", fontsize=10.5)
|
||||
links.grid(linestyle=":", alpha=0.45)
|
||||
links.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
links.set_xlim(0, ZUEGE)
|
||||
for rand in ("top", "right"):
|
||||
links.spines[rand].set_visible(False)
|
||||
|
||||
temperaturen = [t for t, _ in temperaturreihe]
|
||||
ergebnisse = [e for _, e in temperaturreihe]
|
||||
rechts.axhline(start, color=GRAU, linewidth=1.2, linestyle="--", zorder=1)
|
||||
rechts.plot(temperaturen, ergebnisse, "o-", color=INDIGO, markersize=5,
|
||||
linewidth=1.5, zorder=3)
|
||||
bester = int(np.argmin(ergebnisse))
|
||||
rechts.scatter([temperaturen[bester]], [ergebnisse[bester]], s=140,
|
||||
facecolors="none", edgecolors=AMBER, linewidths=2.2, zorder=4)
|
||||
rechts.annotate(f"bestes T = {temperaturen[bester]:g}",
|
||||
(temperaturen[bester], ergebnisse[bester]),
|
||||
textcoords="offset points", xytext=(10, -2),
|
||||
fontsize=8, color=AMBER)
|
||||
rechts.annotate(f"Faustregel: {start:,}".replace(",", "."),
|
||||
(temperaturen[0], start), textcoords="offset points",
|
||||
xytext=(2, 4), fontsize=8, color="#475569")
|
||||
rechts.annotate("hier ist der ganze\nVorsprung aufgezehrt",
|
||||
(temperaturen[-1], ergebnisse[-1]),
|
||||
textcoords="offset points", xytext=(-8, -4),
|
||||
ha="right", va="top", fontsize=8, color="#475569")
|
||||
rechts.set_ylim(min(ergebnisse) - 8, start + 14)
|
||||
|
||||
rechts.set_xscale("log")
|
||||
rechts.set_xticks(temperaturen)
|
||||
rechts.set_xticklabels([f"{t:g}" for t in temperaturen])
|
||||
rechts.set_xlabel("Starttemperatur $T_0$")
|
||||
rechts.set_ylabel("erreichte Rüstzeit (Minuten)")
|
||||
rechts.set_title("Das Optimum ist schmal", fontsize=10.5)
|
||||
rechts.grid(linestyle=":", alpha=0.45)
|
||||
for rand in ("top", "right"):
|
||||
rechts.spines[rand].set_visible(False)
|
||||
|
||||
figur.tight_layout()
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_metaheuristiken_landschaft.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
def zeichne_interaktiv(verlaeufe, start) -> None:
|
||||
"""Dieselben Verlaeufe, aber mit der Temperatur im Hover.
|
||||
|
||||
Das ist der Grund fuer die interaktive Fassung: Im statischen Bild sieht
|
||||
man, DASS die T0=8-Kurve lange oben bleibt. Hier laesst sich an jeder
|
||||
Stelle ablesen, WARUM - naemlich wie wahrscheinlich dort ein Zug typischer
|
||||
Groesse noch angenommen wird.
|
||||
"""
|
||||
figur = go.Figure()
|
||||
figur.add_hline(y=start, line=dict(color=GRAU, width=1.2, dash="dash"),
|
||||
annotation_text=f"Faustregel: {start}",
|
||||
annotation_position="bottom right")
|
||||
|
||||
for (name, farbe, breite), (x, y, bestes, temps) in verlaeufe:
|
||||
beschriftung = []
|
||||
for zug, wert, temperatur in zip(x, y, temps):
|
||||
quote = math.exp(-MEDIAN_ZUG / temperatur) if temperatur > 0 else 0.0
|
||||
beschriftung.append(
|
||||
f"<b>{wert} Minuten</b><br>nach {zug:,} Zügen<br>"
|
||||
f"Temperatur {temperatur:.3f}<br>"
|
||||
f"typischer Verschlechterungszug ({MEDIAN_ZUG} min) würde<br>"
|
||||
f"mit {quote:.1e} angenommen<extra></extra>".replace(",", "."))
|
||||
figur.add_trace(go.Scatter(
|
||||
x=x, y=y, mode="lines", name=f"{name} → {bestes}",
|
||||
line=dict(color=farbe, width=breite + 0.4),
|
||||
hovertemplate=beschriftung))
|
||||
|
||||
figur.update_layout(
|
||||
title="Der Suchverlauf — mit der Temperatur im Hover",
|
||||
xaxis=dict(title="geprüfte Züge"),
|
||||
yaxis=dict(title="Rüstzeit des aktuellen Plans (Minuten)"),
|
||||
height=430, margin=dict(l=70, r=20, t=55, b=50),
|
||||
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
||||
legend=dict(orientation="h", y=-0.2),
|
||||
hovermode="closest")
|
||||
|
||||
os.makedirs(PLOTLY_VERZ, exist_ok=True)
|
||||
pfad = os.path.join(PLOTLY_VERZ, "kap_metaheuristiken_verlauf.html")
|
||||
with open(pfad, "w", encoding="utf-8") as datei:
|
||||
datei.write(figur.to_html(full_html=False, include_plotlyjs=False,
|
||||
div_id="kap-metaheuristiken-verlauf"))
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
matrix = erzeuge_ruestmatrix(N_AUFTRAEGE)
|
||||
start = gesamtruestzeit(faustregel(matrix), matrix)
|
||||
print(f"Instanz: {N_AUFTRAEGE} Auftraege, Faustregel {start} Minuten")
|
||||
|
||||
verlaeufe = []
|
||||
for beschriftung, kalt, temperatur in (
|
||||
(("Bergsteigen", CYAN, 1.3), True, 1.0),
|
||||
(("Annealing $T_0=1$", INDIGO, 1.3), False, 1.0),
|
||||
(("Annealing $T_0=8$", AMBER, 1.0), False, 8.0)):
|
||||
x, y, bestes, temps = suche_mit_verlauf(matrix, temperatur, bergsteigen=kalt)
|
||||
print(f" {beschriftung[0]:<22} bestes {bestes}")
|
||||
verlaeufe.append((beschriftung, (x, y, bestes, temps)))
|
||||
|
||||
temperaturreihe = []
|
||||
for temperatur in (0.5, 1.0, 2.0, 4.0, 8.0):
|
||||
_, _, bestes, _ = suche_mit_verlauf(matrix, temperatur)
|
||||
temperaturreihe.append((temperatur, bestes))
|
||||
print(f" T0={temperatur:<5} -> {bestes}")
|
||||
|
||||
zeichne(verlaeufe, start, temperaturreihe)
|
||||
zeichne_interaktiv(verlaeufe, start)
|
||||
148
bilder_04/erzeuge_min_cost_flow.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_min_cost_flow.py
|
||||
"""
|
||||
Erzeugt das Flussdiagramm zum Graphen-Kapitel:
|
||||
|
||||
bilder_04/kap_graphen_min_cost_flow.svg / .png
|
||||
|
||||
Das Vorgaengerbild zeigte die Instanz von Min_Cost_Flow.py korrekt - aber nur
|
||||
die Instanz. Kosten und Kapazitaeten standen an den Kanten, der FLUSS nicht.
|
||||
Damit fehlte genau das, was das Kapitel erklaert.
|
||||
|
||||
Hier wird das Netzwerk geloest und das Ergebnis eingezeichnet:
|
||||
|
||||
* Kantenbeschriftung "Fluss / Kapazitaet, Kosten je Einheit"
|
||||
* Strichstaerke waechst mit dem Fluss
|
||||
* ausgelastete Kanten hervorgehoben, ungenutzte gestrichelt und blass
|
||||
* Knotenpreise (die Dualwerte der Flusserhaltung) an den Knoten
|
||||
|
||||
Zwei Dinge werden dadurch sichtbar, die im alten Bild fehlten: Die billige
|
||||
Route ueber den Umschlag laeuft voll, und trotzdem muss die teure Direktkante
|
||||
Werk_B -> Kunde_2 bedient werden - weil der Umschlagweg dorthin schon
|
||||
ausgelastet ist. Genau das ist der Punkt des Abschnitts.
|
||||
|
||||
Modell und Loesungsweg sind wortgleich zu Min_Cost_Flow.py, und gerechnet wird
|
||||
hier erneut. Das Skript prueft, dass Zielwert und Fluesse zum Programmlauf
|
||||
passen - stimmt etwas nicht, bricht es ab.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_min_cost_flow.py
|
||||
|
||||
Benoetigt: numpy, scipy, graphviz (Python-Paket) und das Werkzeug 'dot'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import graphviz
|
||||
import numpy as np
|
||||
from scipy.optimize import linprog
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, VIOLETT, GRUEN, AMBER = (
|
||||
"#4338ca", "#0891b2", "#7c3aed", "#059669", "#b45309")
|
||||
TINT = {INDIGO: "#eef2ff", CYAN: "#ecfeff", VIOLETT: "#f5f3ff",
|
||||
GRUEN: "#ecfdf5", AMBER: "#fffbeb"}
|
||||
BLASS = "#cbd5e1"
|
||||
|
||||
# --- Netzwerk: wortgleich zu Min_Cost_Flow.py -------------------------------
|
||||
KNOTEN = ["Werk_A", "Werk_B", "Umschlag", "Kunde_1", "Kunde_2"]
|
||||
KANTEN = [
|
||||
("Werk_A", "Umschlag", 2.0, 15),
|
||||
("Werk_A", "Kunde_1", 5.0, 10),
|
||||
("Werk_B", "Umschlag", 4.0, 10),
|
||||
("Werk_B", "Kunde_2", 6.0, 10),
|
||||
("Umschlag", "Kunde_1", 1.0, 20),
|
||||
("Umschlag", "Kunde_2", 3.0, 10),
|
||||
]
|
||||
SALDO = {"Werk_A": 20, "Werk_B": 10, "Umschlag": 0, "Kunde_1": -15, "Kunde_2": -15}
|
||||
|
||||
ERWARTETE_KOSTEN = 140.0 # aus dem abgedruckten Programmlauf
|
||||
|
||||
|
||||
def loese():
|
||||
"""Flussproblem als LP - derselbe Aufbau wie im Buchprogramm."""
|
||||
knoten_index = {k: i for i, k in enumerate(KNOTEN)}
|
||||
kosten = np.array([k[2] for k in KANTEN])
|
||||
A_eq = np.zeros((len(KNOTEN), len(KANTEN)))
|
||||
for e, (von, nach, _, _) in enumerate(KANTEN):
|
||||
A_eq[knoten_index[von], e] = +1.0
|
||||
A_eq[knoten_index[nach], e] = -1.0
|
||||
b_eq = np.array([SALDO[k] for k in KNOTEN], dtype=float)
|
||||
schranken = [(0, k[3]) for k in KANTEN]
|
||||
ergebnis = linprog(c=kosten, A_eq=A_eq, b_eq=b_eq, bounds=schranken,
|
||||
method="highs")
|
||||
if not ergebnis.success:
|
||||
raise SystemExit(f"Nicht loesbar: {ergebnis.message}")
|
||||
return float(ergebnis.fun), np.asarray(ergebnis.x), np.asarray(ergebnis.eqlin.marginals)
|
||||
|
||||
|
||||
def zahl(wert: float) -> str:
|
||||
text = f"{wert:.2f}".rstrip("0").rstrip(".")
|
||||
if text in ("-0", "-"):
|
||||
text = "0"
|
||||
return text.replace(".", ",")
|
||||
|
||||
|
||||
def zeichne(gesamtkosten: float, fluss: np.ndarray, preise: np.ndarray) -> None:
|
||||
punkt = graphviz.Digraph(format="svg")
|
||||
punkt.attr(rankdir="LR", splines="spline", nodesep="0.5", ranksep="1.1",
|
||||
fontname="DejaVu Sans", bgcolor="transparent", fontsize="12",
|
||||
labelloc="t",
|
||||
label=(f"Kostenminimaler Fluss: {zahl(gesamtkosten)} € für 30 Einheiten\\n"
|
||||
"Kantenbeschriftung: Fluss / Kapazität, Kosten je Einheit"))
|
||||
punkt.attr("node", fontname="DejaVu Sans", fontsize="10")
|
||||
punkt.attr("edge", fontname="DejaVu Sans", fontsize="9")
|
||||
|
||||
preis_von = dict(zip(KNOTEN, preise))
|
||||
for name in KNOTEN:
|
||||
saldo = SALDO[name]
|
||||
if saldo > 0:
|
||||
farbe, rolle = GRUEN, f"Angebot +{saldo}"
|
||||
elif saldo < 0:
|
||||
farbe, rolle = INDIGO, f"Bedarf {saldo}"
|
||||
else:
|
||||
farbe, rolle = VIOLETT, "Durchleitung"
|
||||
punkt.node(name, f"{name.replace('_', ' ')}\\n{rolle}\\n"
|
||||
f"Knotenpreis {zahl(preis_von[name])} €",
|
||||
shape="box", style="filled,rounded", fillcolor=TINT[farbe],
|
||||
color=farbe, penwidth="2.0", margin="0.16,0.09")
|
||||
|
||||
for (von, nach, kosten, kapazitaet), menge in zip(KANTEN, fluss):
|
||||
voll = menge > kapazitaet - 1e-7
|
||||
leer = menge < 1e-7
|
||||
beschriftung = (f"{zahl(menge)} / {kapazitaet}, {zahl(kosten)} €"
|
||||
+ (" ⟵ voll" if voll else ""))
|
||||
if leer:
|
||||
punkt.edge(von, nach, label=f"{beschriftung} (ungenutzt)",
|
||||
color=BLASS, fontcolor="#94a3b8", style="dashed",
|
||||
penwidth="1.0")
|
||||
else:
|
||||
punkt.edge(von, nach, label=beschriftung,
|
||||
color=AMBER if voll else CYAN,
|
||||
fontcolor=AMBER if voll else "#334155",
|
||||
penwidth=f"{1.0 + 3.0 * menge / kapazitaet:.1f}")
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
punkt.format = endung
|
||||
pfad = punkt.render(os.path.join(BILDER, "kap_graphen_min_cost_flow"),
|
||||
cleanup=True)
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gesamtkosten, fluss, preise = loese()
|
||||
print(f"Minimale Transportkosten: {gesamtkosten:.2f} EUR")
|
||||
for (von, nach, kosten, kapazitaet), menge in zip(KANTEN, fluss):
|
||||
marke = " (VOLL)" if menge > kapazitaet - 1e-7 else ""
|
||||
print(f" {von:9} -> {nach:9} {menge:6.1f} / {kapazitaet:3}{marke}")
|
||||
|
||||
if abs(gesamtkosten - ERWARTETE_KOSTEN) > 1e-6:
|
||||
raise SystemExit(f"FEHLER: Zielwert {gesamtkosten} weicht vom "
|
||||
f"abgedruckten Programmlauf ({ERWARTETE_KOSTEN}) ab.")
|
||||
zeichne(gesamtkosten, fluss, preise)
|
||||
208
bilder_04/erzeuge_pareto_front.py
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_pareto_front.py
|
||||
"""
|
||||
Erzeugt das Pareto-Front-Diagramm zum Mehrziel-Kapitel in zwei Fassungen:
|
||||
|
||||
bilder_04/kap_mehrziel_pareto.svg statisch, fuer das PDF
|
||||
bilder_04/kap_mehrziel_pareto.png statisch, Rasterfassung
|
||||
bilder_04/plotly/kap_mehrziel_pareto.html interaktiv, fuer die Website
|
||||
|
||||
Die Instanz ist identisch mit Mehrziel_Pareto.py, und gerechnet wird hier
|
||||
erneut - so koennen Diagramm und Buchtext nicht auseinanderlaufen.
|
||||
|
||||
Das Diagramm zeigt genau die Aussage des Kapitels: Die untere konvexe Huelle
|
||||
(gestrichelt) ist alles, was eine gewichtete Summe je erreichen kann. Die
|
||||
Punkte darueber sind Pareto-optimal und trotzdem fuer kein Gewicht zu haben.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_pareto_front.py
|
||||
|
||||
Benoetigt: numpy, scipy, matplotlib, plotly
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import plotly.graph_objects as go
|
||||
from scipy.optimize import linprog
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
PLOTLY_VERZ = os.path.join(BILDER, "plotly")
|
||||
|
||||
INDIGO, CYAN, AMBER = "#4338ca", "#0891b2", "#b45309"
|
||||
BAHN_TRASSEN = 5
|
||||
|
||||
|
||||
# --- Instanz und Modell: wortgleich zu Mehrziel_Pareto.py -------------------
|
||||
|
||||
def erzeuge_sendungen(anzahl: int = 12, saat: int = 5):
|
||||
rng = np.random.default_rng(saat)
|
||||
kosten = np.zeros((anzahl, 3))
|
||||
co2 = np.zeros((anzahl, 3))
|
||||
for i in range(anzahl):
|
||||
grund_kosten = rng.integers(600, 2400)
|
||||
grund_co2 = rng.integers(400, 1800)
|
||||
kosten[i] = [grund_kosten,
|
||||
grund_kosten * rng.uniform(0.55, 0.85),
|
||||
grund_kosten * rng.uniform(0.70, 1.00)]
|
||||
co2[i] = [grund_co2,
|
||||
grund_co2 * rng.uniform(0.15, 0.35),
|
||||
grund_co2 * rng.uniform(0.40, 0.70)]
|
||||
return np.round(kosten).astype(int), np.round(co2).astype(int)
|
||||
|
||||
|
||||
KOSTEN, CO2 = erzeuge_sendungen()
|
||||
N = len(KOSTEN)
|
||||
|
||||
|
||||
def plane(ziel, co2_grenze=None):
|
||||
gleichungen = np.zeros((N, N * 3))
|
||||
for i in range(N):
|
||||
gleichungen[i, i * 3:(i + 1) * 3] = 1.0
|
||||
ungleichungen = [[1.0 if j == 1 else 0.0 for _ in range(N) for j in range(3)]]
|
||||
grenzen = [float(BAHN_TRASSEN)]
|
||||
if co2_grenze is not None:
|
||||
ungleichungen.append(CO2.reshape(-1).astype(float))
|
||||
grenzen.append(float(co2_grenze))
|
||||
ergebnis = linprog(ziel.reshape(-1).astype(float),
|
||||
A_ub=ungleichungen, b_ub=grenzen,
|
||||
A_eq=gleichungen, b_eq=np.ones(N),
|
||||
bounds=(0, 1), integrality=1, method="highs")
|
||||
if not ergebnis.success:
|
||||
return None
|
||||
plan = np.round(ergebnis.x).astype(int)
|
||||
return int(KOSTEN.reshape(-1) @ plan), int(CO2.reshape(-1) @ plan)
|
||||
|
||||
|
||||
def pareto_front():
|
||||
front = []
|
||||
grenze = plane(KOSTEN)[1]
|
||||
while True:
|
||||
ergebnis = plane(KOSTEN, co2_grenze=grenze)
|
||||
if ergebnis is None:
|
||||
return front
|
||||
front.append(ergebnis)
|
||||
grenze = ergebnis[1] - 1
|
||||
|
||||
|
||||
def untere_huelle(punkte):
|
||||
huelle = []
|
||||
for punkt in sorted(punkte):
|
||||
while len(huelle) >= 2:
|
||||
(x1, y1), (x2, y2) = huelle[-2], huelle[-1]
|
||||
if (x2 - x1) * (punkt[1] - y1) - (y2 - y1) * (punkt[0] - x1) <= 0:
|
||||
huelle.pop()
|
||||
else:
|
||||
break
|
||||
huelle.append(punkt)
|
||||
return huelle
|
||||
|
||||
|
||||
# --- Statische Fassung ------------------------------------------------------
|
||||
|
||||
def zeichne_statisch(front, huelle) -> None:
|
||||
unerreichbar = [p for p in front if p not in huelle]
|
||||
figur, achse = plt.subplots(figsize=(7.2, 4.4))
|
||||
|
||||
hx, hy = zip(*huelle)
|
||||
achse.plot(hx, hy, linestyle="--", color=INDIGO, linewidth=1.4, zorder=1,
|
||||
label="untere konvexe Hülle")
|
||||
achse.scatter(hx, hy, s=70, color=INDIGO, zorder=3,
|
||||
label=f"von Gewichten erreichbar ({len(huelle)})")
|
||||
if unerreichbar:
|
||||
ux, uy = zip(*unerreichbar)
|
||||
achse.scatter(ux, uy, s=90, facecolors="none", edgecolors=AMBER,
|
||||
linewidths=2.0, zorder=4,
|
||||
label=f"Pareto-optimal, aber unerreichbar ({len(unerreichbar)})")
|
||||
|
||||
for x, y in front:
|
||||
achse.annotate(f"{y:,} kg".replace(",", "."), (x, y),
|
||||
textcoords="offset points", xytext=(6, 6), fontsize=7,
|
||||
color="#475569")
|
||||
|
||||
achse.set_xlabel("Transportkosten (EUR)")
|
||||
achse.set_ylabel("CO₂-Ausstoß (kg)")
|
||||
achse.set_title("Pareto-Front: Kosten gegen CO₂", fontsize=11)
|
||||
achse.grid(linestyle=":", alpha=0.5)
|
||||
for rand in ("top", "right"):
|
||||
achse.spines[rand].set_visible(False)
|
||||
achse.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_mehrziel_pareto.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
# --- Interaktive Fassung ----------------------------------------------------
|
||||
|
||||
def zeichne_interaktiv(front, huelle) -> None:
|
||||
unerreichbar = [p for p in front if p not in huelle]
|
||||
guenstigst = front[0]
|
||||
figur = go.Figure()
|
||||
|
||||
hx, hy = zip(*huelle)
|
||||
figur.add_trace(go.Scatter(
|
||||
x=hx, y=hy, mode="lines", line=dict(color=INDIGO, dash="dash", width=1.5),
|
||||
name="untere konvexe Hülle", hoverinfo="skip"))
|
||||
|
||||
def beschriftung(punkt):
|
||||
aufpreis = punkt[0] - guenstigst[0]
|
||||
ersparnis = guenstigst[1] - punkt[1]
|
||||
preis = f"{aufpreis / ersparnis:.2f} EUR je kg" if ersparnis else "Kostenminimum"
|
||||
return (f"<b>{punkt[0]:,} EUR</b><br>{punkt[1]:,} kg CO₂<br>"
|
||||
f"Aufpreis {aufpreis:,} EUR<br>"
|
||||
f"Ersparnis {ersparnis:,} kg<br>{preis}<extra></extra>")
|
||||
|
||||
figur.add_trace(go.Scatter(
|
||||
x=hx, y=hy, mode="markers", marker=dict(size=11, color=INDIGO),
|
||||
name=f"von Gewichten erreichbar ({len(huelle)})",
|
||||
hovertemplate=[beschriftung(p) for p in huelle]))
|
||||
|
||||
if unerreichbar:
|
||||
ux, uy = zip(*unerreichbar)
|
||||
figur.add_trace(go.Scatter(
|
||||
x=ux, y=uy, mode="markers",
|
||||
marker=dict(size=13, color="rgba(0,0,0,0)",
|
||||
line=dict(color=AMBER, width=2.5)),
|
||||
name=f"Pareto-optimal, aber unerreichbar ({len(unerreichbar)})",
|
||||
hovertemplate=[beschriftung(p) for p in unerreichbar]))
|
||||
|
||||
figur.update_layout(
|
||||
title="Pareto-Front: Kosten gegen CO₂",
|
||||
xaxis=dict(title="Transportkosten (EUR)"),
|
||||
yaxis=dict(title="CO₂-Ausstoß (kg)"),
|
||||
height=430, margin=dict(l=70, r=20, t=55, b=50),
|
||||
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
||||
legend=dict(orientation="h", y=-0.2),
|
||||
hovermode="closest")
|
||||
|
||||
os.makedirs(PLOTLY_VERZ, exist_ok=True)
|
||||
pfad = os.path.join(PLOTLY_VERZ, "kap_mehrziel_pareto.html")
|
||||
with open(pfad, "w", encoding="utf-8") as datei:
|
||||
datei.write(figur.to_html(full_html=False, include_plotlyjs=False,
|
||||
div_id="kap-mehrziel-pareto"))
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
front = pareto_front()
|
||||
huelle = untere_huelle(front)
|
||||
print(f"Pareto-Punkte: {len(front)}, davon gestuetzt: {len(huelle)}, "
|
||||
f"unerreichbar: {len(front) - len(huelle)}")
|
||||
zeichne_statisch(front, huelle)
|
||||
zeichne_interaktiv(front, huelle)
|
||||
254
bilder_04/erzeuge_polyeder.py
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_polyeder.py
|
||||
"""
|
||||
Erzeugt die beiden Polyeder-Diagramme zum Fundament- und zum LP-Kapitel:
|
||||
|
||||
bilder_04/kap_fundament_polyeder.svg / .png der zulaessige Bereich
|
||||
bilder_04/kap_lp_simplex_ecken.svg / .png der Eckenlauf des Simplex
|
||||
|
||||
Beide zeigen DASSELBE Modell - das Bot-Allokationsproblem, das sich durch die
|
||||
ersten Kapitel zieht:
|
||||
|
||||
max 150 x1 + 250 x2
|
||||
u.d.N. 2 x1 + 5 x2 <= 40 (vCPU)
|
||||
4 x1 + 6 x2 <= 60 (RAM)
|
||||
x1 <= 8 (Marktlimit)
|
||||
x1, x2 >= 0
|
||||
|
||||
Warum das wichtig ist: Die Vorgaengerbilder zeigten ein voellig anderes LP
|
||||
(2x1+3x2 <= 24, 3x1+2x2 <= 24, Optimum bei (4,8; 4,8)) - unmittelbar neben einer
|
||||
Handrechnung, die mit dem Bot-Modell rechnet und (7,5; 5) herausbekommt. Ein
|
||||
Leser musste annehmen, beides gehoere zusammen.
|
||||
|
||||
Die Ecken werden NICHT hingeschrieben, sondern nach demselben Verfahren
|
||||
bestimmt wie in Visualisierung_Loesungsraum.py: Schnittpunkte je zweier
|
||||
Begrenzungsgeraden, die zulaessig sind. Der Simplex-Weg im zweiten Bild ist der
|
||||
Weg der Handrechnung im LP-Kapitel: (0,0) -> (0,8) -> (7,5; 5), also drei der
|
||||
fuenf Ecken.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_polyeder.py
|
||||
|
||||
Benoetigt: numpy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRUEN = "#4338ca", "#0891b2", "#b45309", "#059669"
|
||||
|
||||
# Modell wortgleich zu Visualisierung_Loesungsraum.py
|
||||
C = np.array([150.0, 250.0])
|
||||
A = np.array([[2.0, 5.0], [4.0, 6.0], [1.0, 0.0]])
|
||||
B = np.array([40.0, 60.0, 8.0])
|
||||
NAMEN = ["vCPU: $2x_1+5x_2 \\leq 40$", "RAM: $4x_1+6x_2 \\leq 60$",
|
||||
"Marktlimit: $x_1 \\leq 8$"]
|
||||
FARBEN = [INDIGO, CYAN, AMBER]
|
||||
|
||||
# Der Weg der Handrechnung im LP-Kapitel: Start im Ursprung, dann x2 aufnehmen
|
||||
# (Pivotspalte -250), dann x1 (Pivotspalte -50).
|
||||
SIMPLEXWEG = [(0.0, 0.0), (0.0, 8.0), (7.5, 5.0)]
|
||||
|
||||
|
||||
def ist_zulaessig(punkt: np.ndarray, tol: float = 1e-7) -> bool:
|
||||
return bool(np.all(A @ punkt <= B + tol) and np.all(punkt >= -tol))
|
||||
|
||||
|
||||
def berechne_ecken() -> np.ndarray:
|
||||
"""Ecken = zulaessige Schnittpunkte je zweier Begrenzungsgeraden.
|
||||
|
||||
Wortgleich zu Visualisierung_Loesungsraum.py - die Koordinaten im Bild
|
||||
stammen also aus derselben Rechnung wie die im Buch abgedruckte Tabelle,
|
||||
nicht aus einer zweiten Quelle.
|
||||
"""
|
||||
geraden = [(A[i], B[i]) for i in range(len(B))]
|
||||
geraden.append((np.array([1.0, 0.0]), 0.0)) # x1 = 0
|
||||
geraden.append((np.array([0.0, 1.0]), 0.0)) # x2 = 0
|
||||
|
||||
ecken: list[np.ndarray] = []
|
||||
for (n1, d1), (n2, d2) in itertools.combinations(geraden, 2):
|
||||
matrix = np.array([n1, n2])
|
||||
if abs(np.linalg.det(matrix)) < 1e-9:
|
||||
continue
|
||||
punkt = np.linalg.solve(matrix, np.array([d1, d2]))
|
||||
if ist_zulaessig(punkt) and not any(np.allclose(punkt, e) for e in ecken):
|
||||
ecken.append(punkt)
|
||||
return np.array(ecken)
|
||||
|
||||
|
||||
# Beschriftungsposition je Ecke, in Punkten relativ zum Eckpunkt. Von Hand
|
||||
# gesetzt und nicht gerechnet: (7,5; 5) und (8; 4,67) liegen eine halbe Einheit
|
||||
# auseinander, und jede automatische Regel legt ihre Beschriftungen uebereinander.
|
||||
# Das ist Layout, keine Aussage - die Koordinaten selbst bleiben gerechnet.
|
||||
VERSATZ = {
|
||||
(0.0, 0.0): (14, 10),
|
||||
(0.0, 8.0): (14, -2),
|
||||
(8.0, 0.0): (-10, 14),
|
||||
(7.5, 5.0): (-12, 16),
|
||||
(8.0, 4.67): (16, -4),
|
||||
}
|
||||
|
||||
|
||||
# Im Simplex-Bild laeuft der Pfad durch zwei dieser Positionen - dort andere.
|
||||
VERSATZ_SIMPLEX = {
|
||||
(0.0, 8.0): (16, 12),
|
||||
(7.5, 5.0): (14, 16),
|
||||
}
|
||||
|
||||
|
||||
def versatz(punkt: np.ndarray, mit_pfad: bool = False) -> tuple:
|
||||
schluessel = (round(float(punkt[0]), 2), round(float(punkt[1]), 2))
|
||||
if mit_pfad and schluessel in VERSATZ_SIMPLEX:
|
||||
return VERSATZ_SIMPLEX[schluessel]
|
||||
return VERSATZ.get(schluessel, (12, 8))
|
||||
|
||||
|
||||
def punkt_text(punkt: np.ndarray) -> str:
|
||||
"""Koordinaten deutsch und auf zwei Stellen - '(8; 4,67)' statt '(8; 4.66667)'."""
|
||||
teile = []
|
||||
for wert in punkt:
|
||||
text = f"{wert:.2f}".rstrip("0").rstrip(".")
|
||||
teile.append(text.replace(".", ","))
|
||||
return f"({teile[0]}; {teile[1]})"
|
||||
|
||||
|
||||
def euro(wert: float) -> str:
|
||||
return f"{wert:,.0f} €".replace(",", ".")
|
||||
|
||||
|
||||
def sortiere_umlaufend(ecken: np.ndarray) -> np.ndarray:
|
||||
"""Ecken gegen den Uhrzeigersinn ordnen, damit das Polygon nicht verknotet."""
|
||||
mitte = ecken.mean(axis=0)
|
||||
winkel = np.arctan2(ecken[:, 1] - mitte[1], ecken[:, 0] - mitte[0])
|
||||
return ecken[np.argsort(winkel)]
|
||||
|
||||
|
||||
def zeichne_rahmen(achse, ecken: np.ndarray) -> None:
|
||||
"""Restriktionsgeraden, zulaessiger Bereich und Achsenbeschriftung."""
|
||||
rand = sortiere_umlaufend(ecken)
|
||||
achse.fill(rand[:, 0], rand[:, 1], color=INDIGO, alpha=0.10, zorder=1)
|
||||
achse.plot(np.append(rand[:, 0], rand[0, 0]),
|
||||
np.append(rand[:, 1], rand[0, 1]),
|
||||
color=INDIGO, linewidth=1.6, zorder=2)
|
||||
|
||||
x = np.linspace(0, 11, 200)
|
||||
for zeile, grenze, name, farbe in zip(A, B, NAMEN, FARBEN):
|
||||
if abs(zeile[1]) < 1e-9: # senkrechte Gerade x1 = c
|
||||
achse.axvline(grenze / zeile[0], color=farbe, linewidth=1.3,
|
||||
linestyle="--", alpha=0.8, label=name)
|
||||
else:
|
||||
achse.plot(x, (grenze - zeile[0] * x) / zeile[1], color=farbe,
|
||||
linewidth=1.3, linestyle="--", alpha=0.8, label=name)
|
||||
|
||||
achse.set_xlim(-0.9, 11.4)
|
||||
achse.set_ylim(-0.9, 11)
|
||||
achse.set_xlabel("$x_1$ — Bots vom Typ A")
|
||||
achse.set_ylabel("$x_2$ — Bots vom Typ B")
|
||||
achse.grid(linestyle=":", alpha=0.45)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
|
||||
|
||||
def schreibe(figur, name: str) -> None:
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"{name}.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
def zeichne_polyeder(ecken: np.ndarray, bestes: np.ndarray) -> None:
|
||||
"""Fundament: alle Ecken mit ihrem Zielwert - der Fundamentalsatz zum Anfassen."""
|
||||
figur, achse = plt.subplots(figsize=(7.2, 5.2))
|
||||
zeichne_rahmen(achse, ecken)
|
||||
|
||||
for ecke in ecken:
|
||||
wert = float(C @ ecke)
|
||||
optimal = np.allclose(ecke, bestes)
|
||||
achse.scatter(*ecke, s=120 if optimal else 55, zorder=4,
|
||||
color=GRUEN if optimal else INDIGO)
|
||||
beschriftung = f"{punkt_text(ecke)}\n{euro(wert)}"
|
||||
if optimal:
|
||||
beschriftung += "\n← Optimum"
|
||||
weg = versatz(ecke)
|
||||
achse.annotate(beschriftung, ecke, textcoords="offset points",
|
||||
xytext=weg, fontsize=8.5,
|
||||
ha="left" if weg[0] >= 0 else "right",
|
||||
va="bottom" if weg[1] >= 0 else "top",
|
||||
color=GRUEN if optimal else "#334155",
|
||||
fontweight="bold" if optimal else "normal")
|
||||
achse.set_title("Der zulässige Bereich und seine Ecken\n"
|
||||
"$\\max\\ 150x_1 + 250x_2$", fontsize=11)
|
||||
achse.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
figur.tight_layout()
|
||||
schreibe(figur, "kap_fundament_polyeder")
|
||||
|
||||
|
||||
def zeichne_simplexweg(ecken: np.ndarray, bestes: np.ndarray) -> None:
|
||||
"""LP-Kapitel: derselbe Bereich, aber der Weg der Handrechnung."""
|
||||
figur, achse = plt.subplots(figsize=(7.2, 5.2))
|
||||
zeichne_rahmen(achse, ecken)
|
||||
|
||||
besucht = [np.array(p) for p in SIMPLEXWEG]
|
||||
unbesucht = [e for e in ecken
|
||||
if not any(np.allclose(e, p) for p in besucht)]
|
||||
for ecke in unbesucht:
|
||||
achse.scatter(*ecke, s=55, color="#94a3b8", zorder=3)
|
||||
weg = versatz(ecke, mit_pfad=True)
|
||||
achse.annotate(punkt_text(ecke), ecke, textcoords="offset points",
|
||||
xytext=weg, fontsize=8, color="#64748b",
|
||||
ha="left" if weg[0] >= 0 else "right",
|
||||
va="bottom" if weg[1] >= 0 else "top")
|
||||
|
||||
weg = np.array(besucht)
|
||||
achse.plot(weg[:, 0], weg[:, 1], color=AMBER, linewidth=2.4, zorder=4)
|
||||
for schritt, punkt in enumerate(besucht):
|
||||
wert = float(C @ punkt)
|
||||
letzter = schritt == len(besucht) - 1
|
||||
achse.scatter(*punkt, s=140 if letzter else 90, zorder=5,
|
||||
color=GRUEN if letzter else AMBER)
|
||||
weg = versatz(punkt, mit_pfad=True)
|
||||
achse.annotate(f"{schritt}. {punkt_text(punkt)}\nZ = {euro(wert)}",
|
||||
punkt, textcoords="offset points", xytext=weg,
|
||||
fontsize=8.5, fontweight="bold",
|
||||
ha="left" if weg[0] >= 0 else "right",
|
||||
va="bottom" if weg[1] >= 0 else "top",
|
||||
color=GRUEN if letzter else "#334155")
|
||||
|
||||
achse.set_title(f"Der Simplex besucht {len(besucht)} von {len(ecken)} Ecken\n"
|
||||
"und keine davon zweimal", fontsize=11)
|
||||
achse.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
figur.tight_layout()
|
||||
schreibe(figur, "kap_lp_simplex_ecken")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ecken = berechne_ecken()
|
||||
werte = ecken @ C
|
||||
bestes = ecken[int(np.argmax(werte))]
|
||||
print(f"{len(ecken)} Ecken gefunden:")
|
||||
for ecke, wert in sorted(zip(ecken.tolist(), werte.tolist()),
|
||||
key=lambda p: -p[1]):
|
||||
print(f" ({ecke[0]:6.2f}; {ecke[1]:5.2f}) Z = {wert:9,.2f} EUR")
|
||||
print(f"Optimum: ({bestes[0]:g}; {bestes[1]:g})")
|
||||
for punkt in SIMPLEXWEG:
|
||||
if not any(np.allclose(np.array(punkt), e) for e in ecken):
|
||||
raise SystemExit(f"FEHLER: {punkt} ist keine Ecke des Polyeders.")
|
||||
zeichne_polyeder(ecken, bestes)
|
||||
zeichne_simplexweg(ecken, bestes)
|
||||
231
bilder_04/erzeuge_runden.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_runden.py
|
||||
"""
|
||||
Erzeugt das Diagramm "Warum Runden scheitert" zum MILP-Kapitel:
|
||||
|
||||
bilder_04/kap_milp_runden.svg statisch, fuer das PDF
|
||||
bilder_04/kap_milp_runden.png Rasterfassung
|
||||
|
||||
Zwei Bilder nebeneinander, beide aus dem Kapitel selbst:
|
||||
|
||||
Links - die Handrechnung des Kapitels, gezeichnet:
|
||||
max x1 + x2 u.d.N. 2 x1 + 2 x2 <= 3, x ganzzahlig
|
||||
Der Witz ist die Entartung: Die Zielfunktion laeuft PARALLEL zur
|
||||
Restriktion, also ist die ganze Kante optimal, nicht eine Ecke.
|
||||
Aufrunden auf (1,1) fuehrt aus dem zulaessigen Bereich heraus,
|
||||
Abrunden auf (0,0) auf den Wert null. Beides sieht man sofort.
|
||||
Rechts - die Messung aus Runden_Gegenbeispiel.py, mit derselben Saat
|
||||
nachgerechnet: Wie oft ist die aufgerundete Loesung unzulaessig,
|
||||
und was kostet Abrunden - mit und ohne gieriges Auffuellen?
|
||||
|
||||
Warum ueberhaupt neu: Das Vorgaengerbild zeigte ein LP, das im Buch nicht
|
||||
vorkommt (LP-Optimum bei (1,8; 3,4), Z = 42,6), ohne die Zielfunktion zu nennen
|
||||
- unmittelbar ueber der Handrechnung, die etwas voellig anderes rechnet.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis, dauert rund eine Minute):
|
||||
python3 bilder_04/erzeuge_runden.py
|
||||
|
||||
Benoetigt: numpy, scipy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy.optimize import linprog
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, AMBER, GRUEN, ROT = (
|
||||
"#4338ca", "#0891b2", "#b45309", "#059669", "#be123c")
|
||||
|
||||
GROESSEN = [(5, 2), (10, 3), (20, 5), (40, 8)]
|
||||
INSTANZEN = 200
|
||||
SAAT = 2026
|
||||
|
||||
|
||||
# --- Messung: wortgleich zu Runden_Gegenbeispiel.py -------------------------
|
||||
|
||||
def erzeuge_instanz(n, m, rng):
|
||||
c = rng.integers(3, 20, size=n).astype(float)
|
||||
A = rng.integers(1, 9, size=(m, n)).astype(float)
|
||||
b = (A.sum(axis=1) * rng.uniform(0.25, 0.45)).round()
|
||||
return c, A, b
|
||||
|
||||
|
||||
def loese_lp(c, A, b, ganzzahlig=False):
|
||||
n = len(c)
|
||||
ergebnis = linprog(
|
||||
c=-c, A_ub=A, b_ub=b, bounds=[(0, None)] * n,
|
||||
integrality=np.ones(n) if ganzzahlig else None,
|
||||
method="highs")
|
||||
return (-ergebnis.fun, ergebnis.x) if ergebnis.success else (None, None)
|
||||
|
||||
|
||||
def abrunden_und_reparieren(x_lp, c, A, b):
|
||||
x = np.floor(x_lp + 1e-9)
|
||||
verbessert = True
|
||||
while verbessert:
|
||||
verbessert = False
|
||||
for j in np.argsort(-c):
|
||||
kandidat = x.copy()
|
||||
kandidat[j] += 1
|
||||
if np.all(A @ kandidat <= b + 1e-9):
|
||||
x = kandidat
|
||||
verbessert = True
|
||||
break
|
||||
return c @ x, x
|
||||
|
||||
|
||||
def messreihe():
|
||||
"""Dieselbe Schleife wie im Buchprogramm, mit derselben Saat.
|
||||
|
||||
Die Saat sitzt VOR der Schleife ueber die Groessen, nicht darin - so wie
|
||||
dort. Nur deshalb kommen hier dieselben Zahlen heraus wie in der
|
||||
abgedruckten Tabelle.
|
||||
"""
|
||||
rng = np.random.default_rng(SAAT)
|
||||
zeilen = []
|
||||
for n, m in GROESSEN:
|
||||
unzulaessig = 0
|
||||
verluste_ab, verluste_gierig = [], []
|
||||
gezaehlt = 0
|
||||
for _ in range(INSTANZEN):
|
||||
c, A, b = erzeuge_instanz(n, m, rng)
|
||||
z_lp, x_lp = loese_lp(c, A, b, ganzzahlig=False)
|
||||
z_ip, _ = loese_lp(c, A, b, ganzzahlig=True)
|
||||
if z_lp is None or z_ip is None or z_ip <= 0:
|
||||
continue
|
||||
gezaehlt += 1
|
||||
x_auf = np.ceil(x_lp - 1e-9)
|
||||
if np.any(A @ x_auf > b + 1e-9):
|
||||
unzulaessig += 1
|
||||
x_ab = np.floor(x_lp + 1e-9)
|
||||
verluste_ab.append(1.0 - (c @ x_ab) / z_ip)
|
||||
z_gierig, _ = abrunden_und_reparieren(x_lp, c, A, b)
|
||||
verluste_gierig.append(1.0 - z_gierig / z_ip)
|
||||
zeilen.append({
|
||||
"name": f"{n}×{m}",
|
||||
"unzulaessig": unzulaessig / gezaehlt,
|
||||
"ab": float(np.mean(verluste_ab)),
|
||||
"gierig": float(np.mean(verluste_gierig)),
|
||||
})
|
||||
return zeilen
|
||||
|
||||
|
||||
# --- Zeichnen ---------------------------------------------------------------
|
||||
|
||||
def zeichne_handrechnung(achse) -> None:
|
||||
"""Die Handrechnung des Kapitels: max x1+x2 u.d.N. 2x1+2x2 <= 3."""
|
||||
ecken = np.array([[0.0, 0.0], [1.5, 0.0], [0.0, 1.5]])
|
||||
achse.fill(ecken[:, 0], ecken[:, 1], color=CYAN, alpha=0.13, zorder=1)
|
||||
achse.plot([1.5, 0.0], [0.0, 1.5], color=CYAN, linewidth=2.0, zorder=3,
|
||||
label="$2x_1+2x_2 = 3$ — zugleich das LP-Optimum")
|
||||
|
||||
# Ganzzahlige Punkte im Fenster, zulaessig und unzulaessig getrennt
|
||||
for x1 in range(0, 3):
|
||||
for x2 in range(0, 3):
|
||||
zulaessig = 2 * x1 + 2 * x2 <= 3
|
||||
achse.scatter(x1, x2, s=48, zorder=4,
|
||||
color=INDIGO if zulaessig else "#cbd5e1")
|
||||
|
||||
achse.scatter([0.75], [0.75], marker="*", s=260, color=ROT, zorder=6)
|
||||
achse.annotate("LP-Lösung $(0{,}75;\\ 0{,}75)$\n$Z_{LP} = 1{,}5$",
|
||||
(0.75, 0.75), textcoords="offset points", xytext=(-12, -6),
|
||||
ha="right", va="top", fontsize=9, color=ROT,
|
||||
fontweight="bold")
|
||||
|
||||
achse.scatter([1], [1], s=150, facecolors="none", edgecolors=AMBER,
|
||||
linewidths=2.4, zorder=6)
|
||||
achse.annotate("aufgerundet $(1;1)$\n$2+2=4>3$ — unzulässig",
|
||||
(1, 1), textcoords="offset points", xytext=(12, -6),
|
||||
fontsize=8.5, color=AMBER, va="top")
|
||||
|
||||
achse.scatter([0], [0], s=150, facecolors="none", edgecolors="#64748b",
|
||||
linewidths=2.2, zorder=6)
|
||||
achse.annotate("abgerundet $(0;0)$\n$Z = 0$ — alles verschenkt",
|
||||
(0, 0), textcoords="offset points", xytext=(14, 8),
|
||||
fontsize=8.5, color="#475569")
|
||||
|
||||
for punkt in ((1, 0), (0, 1)):
|
||||
achse.scatter(*punkt, s=140, color=GRUEN, zorder=7)
|
||||
achse.annotate("wahres Optimum $Z_{IP}=1$", (1, 0),
|
||||
textcoords="offset points", xytext=(12, -4),
|
||||
va="top", fontsize=8.5, color=GRUEN, fontweight="bold")
|
||||
|
||||
achse.set_xlim(-0.35, 2.35)
|
||||
achse.set_ylim(-0.35, 2.35)
|
||||
achse.set_xlabel("$x_1$")
|
||||
achse.set_ylabel("$x_2$")
|
||||
achse.set_title("Die Handrechnung als Bild:\nrunden geht in beide Richtungen schief",
|
||||
fontsize=10.5)
|
||||
achse.grid(linestyle=":", alpha=0.45)
|
||||
achse.legend(fontsize=8, loc="upper right", frameon=False)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
|
||||
|
||||
def zeichne_messung(achse, zeilen) -> None:
|
||||
namen = [z["name"] for z in zeilen]
|
||||
stellen = np.arange(len(zeilen))
|
||||
breite = 0.26
|
||||
|
||||
achse.bar(stellen - breite, [z["unzulaessig"] * 100 for z in zeilen],
|
||||
breite, color=AMBER, label="Aufrunden unzulässig")
|
||||
achse.bar(stellen, [z["ab"] * 100 for z in zeilen],
|
||||
breite, color=ROT, label="Abrunden: mittlerer Verlust")
|
||||
achse.bar(stellen + breite, [z["gierig"] * 100 for z in zeilen],
|
||||
breite, color=GRUEN, label="abrunden + gierig auffüllen")
|
||||
|
||||
for stelle, zeile in zip(stellen, zeilen):
|
||||
for versatz, wert in ((-breite, zeile["unzulaessig"]),
|
||||
(0.0, zeile["ab"]), (breite, zeile["gierig"])):
|
||||
achse.annotate(f"{wert * 100:.0f}", (stelle + versatz, wert * 100),
|
||||
textcoords="offset points", xytext=(0, 3),
|
||||
ha="center", fontsize=7.5, color="#334155")
|
||||
|
||||
achse.set_xticks(stellen)
|
||||
achse.set_xticklabels(namen)
|
||||
achse.set_xlabel("Instanzgröße (Variablen × Nebenbedingungen)")
|
||||
achse.set_ylabel("Anteil bzw. Verlust (%)")
|
||||
achse.set_ylim(0, 118)
|
||||
achse.set_title(f"Gemessen an je {INSTANZEN} Zufallsinstanzen:\n"
|
||||
"Aufrunden wird mit der Größe hoffnungslos", fontsize=10.5)
|
||||
achse.grid(axis="y", linestyle=":", alpha=0.45)
|
||||
achse.legend(fontsize=8, loc="upper center", frameon=False, ncol=1)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
|
||||
|
||||
def zeichne(zeilen) -> None:
|
||||
figur, (links, rechts) = plt.subplots(1, 2, figsize=(11.0, 4.8))
|
||||
zeichne_handrechnung(links)
|
||||
zeichne_messung(rechts, zeilen)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_milp_runden.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
zeilen = messreihe()
|
||||
print(f"{'n x m':>8} | {'Aufrunden unzul.':>17} | {'Abrunden':>10} | {'gierig':>8}")
|
||||
print("-" * 54)
|
||||
for zeile in zeilen:
|
||||
print(f"{zeile['name']:>8} | {zeile['unzulaessig'] * 100:>16.1f} % | "
|
||||
f"{zeile['ab'] * 100:>9.1f} % | {zeile['gierig'] * 100:>7.1f} %")
|
||||
zeichne(zeilen)
|
||||
132
bilder_04/erzeuge_solverwahl_diagramme.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_solverwahl_diagramme.py
|
||||
"""
|
||||
Erzeugt die beiden Entscheidungs-Flussdiagramme "Welches Werkzeug passt zu
|
||||
meinem Problem?" fuer die Auftakte von Teil II und Teil III:
|
||||
|
||||
bilder_04/teil2_solverwahl.svg / .png deterministische Verfahren
|
||||
bilder_04/teil3_solverwahl.svg / .png Nichtlinearitaet und Unsicherheit
|
||||
|
||||
Beide entstehen mit Graphviz. Die Farben folgen der Buchpalette
|
||||
(Indigo, Cyan, Violett, Gruen, Amber), die Kastenform codiert die Rolle:
|
||||
Raute = Frage, abgerundeter Kasten = Empfehlung.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_solverwahl_diagramme.py
|
||||
|
||||
Benoetigt: graphviz (Python-Paket) und das Kommandozeilenwerkzeug 'dot'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import graphviz
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, CYAN, VIOLETT, GRUEN, AMBER = (
|
||||
"#4338ca", "#0891b2", "#7c3aed", "#059669", "#b45309")
|
||||
TINT = {INDIGO: "#eef2ff", CYAN: "#ecfeff", VIOLETT: "#f5f3ff",
|
||||
GRUEN: "#ecfdf5", AMBER: "#fffbeb"}
|
||||
|
||||
|
||||
def neuer_graph(titel: str) -> graphviz.Digraph:
|
||||
punkt = graphviz.Digraph(format="svg")
|
||||
punkt.attr(rankdir="TB", splines="polyline", nodesep="0.35", ranksep="0.45",
|
||||
fontname="DejaVu Sans", label=titel, labelloc="t",
|
||||
fontsize="13", bgcolor="transparent")
|
||||
punkt.attr("node", fontname="DejaVu Sans", fontsize="10")
|
||||
punkt.attr("edge", fontname="DejaVu Sans", fontsize="9", color="#5c6380")
|
||||
return punkt
|
||||
|
||||
|
||||
def frage(punkt: graphviz.Digraph, name: str, text: str, farbe: str = INDIGO) -> None:
|
||||
punkt.node(name, text, shape="diamond", style="filled",
|
||||
fillcolor=TINT[farbe], color=farbe, penwidth="1.6",
|
||||
margin="0.08,0.02")
|
||||
|
||||
|
||||
def empfehlung(punkt: graphviz.Digraph, name: str, text: str, farbe: str) -> None:
|
||||
punkt.node(name, text, shape="box", style="filled,rounded",
|
||||
fillcolor=TINT[farbe], color=farbe, penwidth="2.0",
|
||||
margin="0.16,0.09")
|
||||
|
||||
|
||||
def schreibe(punkt: graphviz.Digraph, name: str) -> None:
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
punkt.format = endung
|
||||
pfad = punkt.render(os.path.join(BILDER, name), cleanup=True)
|
||||
print(f"geschrieben: {pfad}")
|
||||
|
||||
|
||||
# --- Teil II: deterministische Optimierung ----------------------------------
|
||||
|
||||
def teil2() -> None:
|
||||
punkt = neuer_graph("Welches Werkzeug passt zu meinem Problem? (Teil II)")
|
||||
|
||||
frage(punkt, "f1", "Fahrzeuge, Depots,\nZeitfenster?")
|
||||
frage(punkt, "f2", "Ja/Nein-Entscheidungen,\nZuordnungen, Reihenfolgen?")
|
||||
frage(punkt, "f3", "Zielfunktion und alle\nNebenbedingungen linear?")
|
||||
frage(punkt, "f4", "Logische Regeln\n(wenn-dann, entweder-oder)\noder Zeitplanung?")
|
||||
frage(punkt, "f5", "Nur Fluss oder\nZuordnung im Netzwerk?")
|
||||
|
||||
empfehlung(punkt, "routing", "OR-Tools Routing\n(Kapitel Graphen)", AMBER)
|
||||
empfehlung(punkt, "cpsat", "CP-SAT\n(Kapitel CP-SAT)", VIOLETT)
|
||||
empfehlung(punkt, "milp", "MILP: HiGHS / CP-SAT\n(Kapitel MILP)", CYAN)
|
||||
empfehlung(punkt, "netz", "Spezialalgorithmus:\nMin-Cost-Flow,\nUngarische Methode", GRUEN)
|
||||
empfehlung(punkt, "lp", "LP: scipy.linprog\noder highspy\n(Kapitel LP)", INDIGO)
|
||||
empfehlung(punkt, "teil3", "weiter in Teil III", AMBER)
|
||||
|
||||
punkt.edge("f1", "routing", "ja")
|
||||
punkt.edge("f1", "f2", "nein")
|
||||
punkt.edge("f2", "f4", "ja")
|
||||
punkt.edge("f2", "f3", "nein")
|
||||
punkt.edge("f4", "cpsat", "ja")
|
||||
punkt.edge("f4", "milp", "nein\n(Fixkosten,\nKardinalität)")
|
||||
punkt.edge("f3", "f5", "ja")
|
||||
punkt.edge("f3", "teil3", "nein")
|
||||
punkt.edge("f5", "netz", "ja")
|
||||
punkt.edge("f5", "lp", "nein")
|
||||
|
||||
schreibe(punkt, "teil2_solverwahl")
|
||||
|
||||
|
||||
# --- Teil III: Nichtlinearitaet, Unsicherheit, Dynamik ----------------------
|
||||
|
||||
def teil3() -> None:
|
||||
punkt = neuer_graph("Welches Werkzeug passt zu meinem Problem? (Teil III)")
|
||||
|
||||
frage(punkt, "g1", "Sind die Daten sicher\nbekannt?")
|
||||
frage(punkt, "g2", "Entscheidung fällt in\nmehreren Stufen\nüber die Zeit?")
|
||||
frage(punkt, "g3", "Wahrscheinlichkeiten\nbekannt?")
|
||||
frage(punkt, "g4", "Zielfunktion konvex?\n(Quadrate, Normen,\nlog/exp konvex kombiniert)")
|
||||
frage(punkt, "g5", "Quadratisch mit\nlinearen Nebenbedingungen?")
|
||||
|
||||
empfehlung(punkt, "dp", "Dynamische Programmierung\n(Bellman)", AMBER)
|
||||
empfehlung(punkt, "stoch", "Zweistufige stochastische\nOptimierung, Szenarien", VIOLETT)
|
||||
empfehlung(punkt, "robust", "Robuste Optimierung\n(Worst Case)", VIOLETT)
|
||||
empfehlung(punkt, "qp", "QP über CVXPY\n(OSQP, Clarabel)", CYAN)
|
||||
empfehlung(punkt, "konvex", "CVXPY\n— prüft Konvexität selbst", GRUEN)
|
||||
empfehlung(punkt, "nichtkonvex", "scipy.optimize.minimize\nmit mehreren Startpunkten\n— nur lokales Optimum!", AMBER)
|
||||
|
||||
punkt.edge("g1", "g4", "ja")
|
||||
punkt.edge("g1", "g2", "nein")
|
||||
punkt.edge("g2", "dp", "ja")
|
||||
punkt.edge("g2", "g3", "nein")
|
||||
punkt.edge("g3", "stoch", "ja")
|
||||
punkt.edge("g3", "robust", "nein,\nnur Bandbreiten")
|
||||
punkt.edge("g4", "g5", "ja")
|
||||
punkt.edge("g4", "nichtkonvex", "nein")
|
||||
punkt.edge("g5", "qp", "ja")
|
||||
punkt.edge("g5", "konvex", "nein")
|
||||
|
||||
schreibe(punkt, "teil3_solverwahl")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
teil2()
|
||||
teil3()
|
||||
338
bilder_04/erzeuge_titelseite.py
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_titelseite.py
|
||||
"""
|
||||
Baut die Titelseite bilder_04/titelseite.svg (und die HTML-Kopie
|
||||
OR_HTML_04/bilder_04/titelseite.svg) als SVG mit matplotlib. Das Datum im Fußband
|
||||
('Stand: <datum>') wird dynamisch aus datetime.now() gesetzt, damit es nach
|
||||
jedem Build den aktuellen Stand zeigt.
|
||||
|
||||
Die Koordinaten unten sind SVG-Punkte (595.44 x 841.68, A4 @ 72 dpi). Die
|
||||
y-Achse wird invertiert (set_ylim(841.68, 0)), sodass y von oben zählt und
|
||||
die Werte direkt mit der alten, von Hand erzeugten titelseite.svg
|
||||
übereinstimmen.
|
||||
|
||||
Design (aus der Vorgängerversion abgeleitet):
|
||||
- weißer Hintergrund
|
||||
- 5 farbige Akzentbalken oben
|
||||
- 7 konzentrische Ringe oben rechts (indigo, transparent)
|
||||
- violette Trennlinie unter dem Untertitel
|
||||
- grüne, konkave 'Optimierungskurve' mit rotem Stern-Marker (Optimum)
|
||||
- große Solver-Karte (hell, violetter Rand) mit vier kleinen farbigen
|
||||
Solver-Karten (HiGHS indigo, SciPy grün, Google OR-Tools cyan, CVXPY violett)
|
||||
- indigo Fußband unten mit Autor (weiß) und Stand-Datum (hellviolett)
|
||||
|
||||
Aufruf:
|
||||
python3 bilder_04/erzeuge_titelseite.py
|
||||
(wird vom Build-Skript build_version_03.py vor dem PDF-/HTML-Bau aufgerufen).
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Circle, FancyBboxPatch, Rectangle
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe. Ohne diese beiden Vorkehrungen unterscheiden sich
|
||||
# zwei Laeufe - und sogar die zwei Kopien AUS DEMSELBEN Lauf - in zwei Punkten:
|
||||
# * svg.hashsalt: matplotlib wuerfelt sonst die clip-path-IDs je Figure neu.
|
||||
# * metadata={"Date": None} beim Speichern: sonst steht der Zeitstempel des
|
||||
# Schreibvorgangs (auf die Mikrosekunde) in den SVG-Metadaten.
|
||||
# Beides ist unsichtbar, macht aber jeden git-diff unlesbar und die beiden
|
||||
# Kopien bilder_04/ und OR_HTML_04/bilder_04/ dauerhaft verschieden. Die Datei
|
||||
# aendert sich jetzt nur noch, wenn sich das SICHTBARE Datum im Fussband
|
||||
# aendert - also einmal pro Tag.
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
# Hardcodierte deutsche Monatsnamen statt strftime("%B") - so ist das Datum
|
||||
# unabhängig von der gesetzten Locale (de_DE.UTF-8) immer korrekt geschrieben,
|
||||
# auch in CI-/Docker-/Headless-Umgebungen ohne deutsche Locale.
|
||||
MONATE = ["Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||
"Juli", "August", "September", "Oktober", "November", "Dezember"]
|
||||
|
||||
# A4 bei 72 dpi -> 595.44 x 841.68 pt (matplotlib figure-Koordinaten).
|
||||
BREITE, HOEHE = 595.44, 841.68
|
||||
|
||||
# Geometrie des Mittelteils. _design() zeichnet danach, _texte() setzt danach -
|
||||
# beide lesen aus DIESEN Werten, damit Kasten und Text nie auseinanderlaufen.
|
||||
# Trennlinie und Kartenoberkante sind gegenueber dem urspruenglichen Entwurf
|
||||
# (336.7 / 368.7) nach oben gerueckt: Der Untertitel braucht seit der
|
||||
# Titelaenderung nur noch zwei statt vier Zeilen, und der gewonnene Platz kommt
|
||||
# der Solver-Karte zugute, die jetzt zwei Absaetze traegt.
|
||||
TRENNLINIE_Y = 308.0
|
||||
KARTE_X0, KARTE_BREITE = 64.30752, 466.82496
|
||||
KARTE_Y0, KARTE_Y1 = 340.0, 481.44096
|
||||
|
||||
# Solver-Karten (x0, Breite, Farbe, Label) - bewusst NICHT gleich breit:
|
||||
# "Google OR-Tools" ist deutlich laenger als die anderen Labels und liefe bei
|
||||
# einheitlicher Breite ueber die Karte hinaus - da das Label weiss ist, wuerde
|
||||
# der ueberstehende Teil auf dem weissen Seitenhintergrund unsichtbar. Reihe
|
||||
# beginnt bei x=71.4528 (linker Rand wie Titel/Untertitel) und endet bei
|
||||
# x=523.9872 (wie die violette Trennlinie), dazwischen 3 Luecken von je 10pt.
|
||||
KARTEN_Y0, KARTEN_Y1 = 494.06616, 541.20024
|
||||
KARTEN = [
|
||||
(71.4528, 94.0, "#4338ca", "HiGHS"),
|
||||
(175.4528, 94.0, "#059669", "SciPy"),
|
||||
(279.4528, 140.5344, "#0891b2", "Google OR-Tools"),
|
||||
(429.9872, 94.0, "#7c3aed", "CVXPY"),
|
||||
]
|
||||
|
||||
|
||||
def datum_string() -> str:
|
||||
"""Heutiges Datum als '5. September 2026' (locale-unabhängig)."""
|
||||
n = datetime.now()
|
||||
return f"{n.day}. {MONATE[n.month - 1]} {n.year}"
|
||||
|
||||
|
||||
def _ring(ax, x, y, r):
|
||||
ax.add_patch(Circle((x, y), r, fill=False, ec="#4338ca", alpha=0.1,
|
||||
lw=1.4, clip_on=True))
|
||||
|
||||
|
||||
def _design(ax):
|
||||
# Weißer Hintergrund.
|
||||
ax.add_patch(Rectangle((0, 0), BREITE, HOEHE, facecolor="white",
|
||||
edgecolor="none", zorder=0))
|
||||
|
||||
# 5 farbige Akzentbalken oben (Leiste), y 34.51..46.29, je 90.51 breit.
|
||||
balken_farben = ["#4338ca", "#0891b2", "#7c3aed", "#059669", "#d97706"]
|
||||
for i, farbe in enumerate(balken_farben):
|
||||
ax.add_patch(Rectangle((71.4528 + i * 90.50688, 34.50888),
|
||||
90.50688, 11.78352, facecolor=farbe,
|
||||
edgecolor="none", zorder=1))
|
||||
|
||||
# 7 konzentrische Ringe oben rechts, Zentrum (553.76, 84.17).
|
||||
for r in (42.084, 77.154, 112.224, 147.294, 182.364, 217.434, 252.504):
|
||||
_ring(ax, 553.7592, 84.168, r)
|
||||
|
||||
# Große Solver-Karte (abgerundet): (64.31, 368.66) bis (531.13, 481.44),
|
||||
# Radius ~11.91 (Q-Offset in der Original-SVG).
|
||||
ax.add_patch(FancyBboxPatch(
|
||||
(KARTE_X0, KARTE_Y0), KARTE_BREITE, KARTE_Y1 - KARTE_Y0,
|
||||
boxstyle="round,pad=0,rounding_size=11.9098",
|
||||
facecolor="#f5f3ff", edgecolor="#7c3aed", lw=1.6, zorder=1))
|
||||
|
||||
# Vier farbige Solver-Karten (HiGHS, SciPy, Google OR-Tools, CVXPY),
|
||||
# individuelle Breiten (siehe KARTEN), y 494.07..541.20.
|
||||
for x0, breite, farbe, _label in KARTEN:
|
||||
ax.add_patch(FancyBboxPatch(
|
||||
(x0, KARTEN_Y0), breite, KARTEN_Y1 - KARTEN_Y0,
|
||||
boxstyle="round,pad=0,rounding_size=11.9098",
|
||||
facecolor=farbe, edgecolor="none", zorder=2))
|
||||
|
||||
# Violette Trennlinie unter dem Untertitel, x 71.45..523.99.
|
||||
ax.plot([71.4528, 523.9872], [TRENNLINIE_Y, TRENNLINIE_Y], color="#7c3aed",
|
||||
lw=2.5, solid_capstyle="butt", zorder=3)
|
||||
|
||||
# Indigo Fußband unten: (0, 757.51) bis (595.44, 841.68).
|
||||
ax.add_patch(Rectangle((0, 757.512), BREITE, HOEHE - 757.512,
|
||||
facecolor="#4338ca", edgecolor="none", zorder=1))
|
||||
|
||||
# Grüne 'Optimierungskurve' (konkav, abflachend) von (71.45, 673.34) nach
|
||||
# (523.99, 639.68) - approximiert die dekorative Kurve der Vorgängerversion
|
||||
# (dort 99 geglättete Stützpunkte, hier eine konkave Wurzelfunktion, die
|
||||
# denselben visuellen Eindruck erzeugt).
|
||||
import numpy as np
|
||||
xs = np.linspace(71.4528, 523.9872, 100)
|
||||
t = (xs - 71.4528) / (523.9872 - 71.4528)
|
||||
ys = 673.344 - 33.664 * np.sqrt(t) # konkav: steiler Start, flaches Ende
|
||||
ax.plot(xs, ys, color="#059669", lw=3, solid_capstyle="butt", zorder=3)
|
||||
# Roter Stern-Marker ('Optimum') bei (446.28, 641.67) auf der Kurve.
|
||||
ax.plot([446.279273], [641.670502], marker="*", color="#e11d48",
|
||||
markersize=12, mec="#e11d48", mfc="#e11d48", zorder=4)
|
||||
|
||||
|
||||
# --- Textumbruch: gemessen statt geschaetzt -------------------------------
|
||||
# Bis zur Titelaenderung standen alle Textzeilen mit von Hand vermessenen
|
||||
# Koordinaten hier - uebernommen aus der urspruenglichen, handgezeichneten SVG.
|
||||
# Das traegt genau so lange, wie sich der Text nicht aendert: Der neue Titel ist
|
||||
# knapp dreimal so lang wie der alte, der Untertitel mehr als doppelt so lang.
|
||||
# Deshalb werden Umbruch und Schriftgroesse jetzt GEMESSEN. Wer den Titel das
|
||||
# naechste Mal aendert, aendert nur noch die Zeichenketten weiter unten.
|
||||
|
||||
def _textbreite(ax, text, groesse, fett=False) -> float:
|
||||
"""Breite eines Textes in Seitenpunkten - ueber den echten Renderer."""
|
||||
marke = ax.text(0, 0, text, fontsize=groesse,
|
||||
fontweight="bold" if fett else "normal")
|
||||
kasten = marke.get_window_extent(
|
||||
renderer=ax.figure.canvas.get_renderer()).transformed(
|
||||
ax.transData.inverted())
|
||||
marke.remove()
|
||||
return abs(kasten.width)
|
||||
|
||||
|
||||
def _umbrechen(ax, text, groesse, maxbreite, fett=False) -> list[str]:
|
||||
"""Bricht 'text' so um, dass keine Zeile breiter als 'maxbreite' wird."""
|
||||
zeilen, zeile = [], ""
|
||||
for wort in text.split():
|
||||
probe = f"{zeile} {wort}".strip()
|
||||
if not zeile or _textbreite(ax, probe, groesse, fett) <= maxbreite:
|
||||
zeile = probe
|
||||
else:
|
||||
zeilen.append(zeile)
|
||||
zeile = wort
|
||||
if zeile:
|
||||
zeilen.append(zeile)
|
||||
return zeilen
|
||||
|
||||
|
||||
def _setze_block(ax, zeilen, x, y0, groesse, durchschuss, **stil) -> float:
|
||||
"""Setzt die Zeilen ab Grundlinie y0 und liefert die Grundlinie danach."""
|
||||
for i, zeile in enumerate(zeilen):
|
||||
ax.text(x, y0 + i * groesse * durchschuss, zeile,
|
||||
fontsize=groesse, zorder=5, **stil)
|
||||
return y0 + (len(zeilen) - 1) * groesse * durchschuss
|
||||
|
||||
|
||||
# --- Die Texte der Titelseite ----------------------------------------------
|
||||
# Nur diese Zeichenketten aendern, wenn das Buch anders heissen soll. Der Text
|
||||
# stammt aus Kritik_und_Verbesserungsvorschlaege/NEUER_TITEL.md; die beiden
|
||||
# Absaetze KOMPENDIUM und ANWENDUNGEN
|
||||
# stehen gemeinsam in der grossen Solver-Karte.
|
||||
#
|
||||
# Im TITEL steht ein \n: An dieser Stelle wird bewusst umbrochen. Ohne ihn
|
||||
# wuerde automatisch umbrochen und "mit Python" auseinandergerissen. Die Breite
|
||||
# jeder Zeile wird in beiden Faellen geprueft.
|
||||
#
|
||||
# Warum drei Zeilen und nicht zwei: "Entscheidungsfindung" ist bei 34 pt schon
|
||||
# 429 pt breit und damit fast so breit wie der ganze Satzspiegel (453 pt). Jede
|
||||
# zweizeilige Aufteilung braeuchte mindestens 649 pt - das passt bei keiner
|
||||
# lesbaren Schriftgroesse.
|
||||
TITEL = "Optimierte\nEntscheidungsfindung\nmit Python"
|
||||
UNTERTITEL = ("Von der mathematischen Modellierung zu praktischen "
|
||||
"intelligenten Lösungen für Betrieb, Technik und Finanzmärkte")
|
||||
KOMPENDIUM = ("Ein praxisorientiertes Kompendium für Entscheider mit "
|
||||
"Constraint Programming, Vektor- und Matrixmethoden sowie "
|
||||
"HiGHS, SciPy, Google OR-Tools und CVXPY.")
|
||||
ANWENDUNGEN = ("Von Personaleinsatz-, Schicht- und Vertretungsplänen, "
|
||||
"Fahrtrouten, Logistik und Maschinen- und Netzwerkauslastung "
|
||||
"über Energieverteilung "
|
||||
"und Ressourcenplanung bis zur Portfoliooptimierung und besseren "
|
||||
"Entscheidungen an den Finanzmärkten – mit Python modellieren, "
|
||||
"optimieren und belegbar fundierte Entscheidungen treffen.")
|
||||
AUTOR = "Autor / Herausgeber: Dieter Schlüter"
|
||||
|
||||
# Satzspiegel: linker Rand und rechte Kante wie Trennlinie und Kartenreihe.
|
||||
LINKS, RECHTS = 71.4528, 523.9872
|
||||
SATZBREITE = RECHTS - LINKS
|
||||
|
||||
# Schriftgroessen und Durchschuss (Faktor auf die Schriftgroesse). Nachgemessen,
|
||||
# nicht geraten: Bei 34 pt ist "Entscheidungsfindung" 429 pt breit und bleibt
|
||||
# damit im Satzspiegel von 453 pt; bei 36 pt waeren es 454 pt - eines zu viel.
|
||||
TITEL_GROESSE, TITEL_DURCHSCHUSS = 34, 1.40
|
||||
# Untertitel 14 statt 15 pt: Bei 15 pt braucht er drei Zeilen, und die dritte
|
||||
# traegt nur noch das Wort "Finanzmärkte" - eine Hurenkind-Zeile mitten auf dem
|
||||
# Titelblatt. Bei 14 pt sind es zwei ausgeglichene Zeilen.
|
||||
UNTER_GROESSE, UNTER_DURCHSCHUSS = 14, 1.23
|
||||
# Kartentext 11.5 statt 12 pt: Der Text ist auf neun Zeilen gewachsen; bei 12 pt
|
||||
# blieben nur noch 9 pt Luft ueber und unter dem Block, und der Text geriete in
|
||||
# die abgerundeten Kartenecken (Radius 11.9 pt).
|
||||
KOMP_GROESSE, KOMP_DURCHSCHUSS = 11.5, 1.27
|
||||
KOMP_BREITE = 440.0 # nutzbare Textbreite in der Karte
|
||||
|
||||
# Anker des Satzes.
|
||||
TITEL_START = 135.0 # erste Grundlinie des Haupttitels
|
||||
ABSTAND_TITEL_UNTER = 42.0 # Luft zwischen Titel- und Untertitelblock
|
||||
|
||||
|
||||
def _texte(ax, datum):
|
||||
# Haupttitel: die letzte Zeile ("mit Python") in Anthrazit statt Indigo -
|
||||
# so bleibt die Zweifarbigkeit des urspruenglichen Entwurfs erhalten, in
|
||||
# dem "Operations Research" indigo und "mit Python" dunkel gesetzt war.
|
||||
# Ein \n im TITEL heisst: hier wird umbrochen. Sonst automatisch.
|
||||
if "\n" in TITEL:
|
||||
titelzeilen = TITEL.split("\n")
|
||||
zu_breit = [z for z in titelzeilen
|
||||
if _textbreite(ax, z, TITEL_GROESSE, True) > SATZBREITE]
|
||||
if zu_breit:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Diese Titelzeile passt bei {TITEL_GROESSE} pt "
|
||||
f"nicht in den Satzspiegel ({SATZBREITE:.0f} pt): {zu_breit[0]!r}")
|
||||
else:
|
||||
titelzeilen = _umbrechen(ax, TITEL, TITEL_GROESSE, SATZBREITE, fett=True)
|
||||
for i, zeile in enumerate(titelzeilen):
|
||||
ax.text(LINKS, TITEL_START + i * TITEL_GROESSE * TITEL_DURCHSCHUSS,
|
||||
zeile, fontsize=TITEL_GROESSE, fontweight="bold", zorder=5,
|
||||
color="#1a1a2e" if i == len(titelzeilen) - 1 else "#4338ca")
|
||||
titel_ende = TITEL_START + (len(titelzeilen) - 1) * TITEL_GROESSE * TITEL_DURCHSCHUSS
|
||||
|
||||
# Untertitel direkt darunter; die Zeilenzahl ergibt sich aus dem Umbruch.
|
||||
unterzeilen = _umbrechen(ax, UNTERTITEL, UNTER_GROESSE, SATZBREITE)
|
||||
unter_ende = _setze_block(ax, unterzeilen, LINKS,
|
||||
titel_ende + ABSTAND_TITEL_UNTER,
|
||||
UNTER_GROESSE, UNTER_DURCHSCHUSS, color="#334155")
|
||||
# Sicherung: Der Untertitel darf die Trennlinie nicht beruehren. Lieber ein
|
||||
# Abbruch beim Erzeugen als ein Titelblatt, auf dem Text auf der Linie liegt.
|
||||
if unter_ende > TRENNLINIE_Y - 8:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Der Untertitel reicht bis y={unter_ende:.1f} und "
|
||||
f"kollidiert mit der Trennlinie bei y={TRENNLINIE_Y}. "
|
||||
f"Schriftgroesse oder Startpunkt anpassen.")
|
||||
|
||||
# KOMPENDIUM-Label
|
||||
ax.text(LINKS, 84.168, "K O M P E N D I U M",
|
||||
fontsize=13, color="#0891b2", zorder=5)
|
||||
|
||||
# Die beiden Absaetze in der grossen Solver-Karte, durch eine Leerzeile
|
||||
# getrennt und als Block vertikal zentriert. Zentriert gesetzt statt links,
|
||||
# weil die Karte breiter ist als der Satzspiegel.
|
||||
kartenmitte_x = KARTE_X0 + KARTE_BREITE / 2
|
||||
kartenzeilen = (_umbrechen(ax, KOMPENDIUM, KOMP_GROESSE, KOMP_BREITE)
|
||||
+ [""]
|
||||
+ _umbrechen(ax, ANWENDUNGEN, KOMP_GROESSE, KOMP_BREITE))
|
||||
hoehe = (len(kartenzeilen) - 1) * KOMP_GROESSE * KOMP_DURCHSCHUSS
|
||||
start = (KARTE_Y0 + KARTE_Y1) / 2 - hoehe / 2 + KOMP_GROESSE * 0.35
|
||||
# Sicherung: Der Block darf nicht ueber den Kartenrand hinauslaufen.
|
||||
if start - KOMP_GROESSE < KARTE_Y0 or start + hoehe > KARTE_Y1 - 4:
|
||||
raise SystemExit(
|
||||
f"Titelblatt: Der Kartentext ({len(kartenzeilen)} Zeilen) passt "
|
||||
f"nicht in die Karte ({KARTE_Y1 - KARTE_Y0:.0f} pt hoch). "
|
||||
f"Schriftgroesse verkleinern oder Karte vergroessern.")
|
||||
_setze_block(ax, kartenzeilen, kartenmitte_x, start,
|
||||
KOMP_GROESSE, KOMP_DURCHSCHUSS, color="#1a1a2e", ha="center")
|
||||
|
||||
# Autor (im Fußband, weiß)
|
||||
ax.text(LINKS, 793.402715, AUTOR,
|
||||
fontsize=14, color="#ffffff", zorder=5)
|
||||
# text_7: Stand-Datum (im Fußband, hellviolett) - DYNAMISCH
|
||||
ax.text(71.4528, 818.05749, f"Stand: {datum}",
|
||||
fontsize=12, color="#c7d2fe", zorder=5)
|
||||
# Solver-Namen, zentriert in ihrer Karte (weiß). ha/va="center"
|
||||
# macht die Zentrierung unabhängig von Schriftmetriken - robuster als
|
||||
# von Hand vermessene x/y-Offsets.
|
||||
mitte_y = (KARTEN_Y0 + KARTEN_Y1) / 2
|
||||
for x0, breite, _farbe, label in KARTEN:
|
||||
ax.text(x0 + breite / 2, mitte_y, label,
|
||||
fontsize=13, color="#ffffff", ha="center", va="center",
|
||||
zorder=5)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
basis = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ausgaben = [
|
||||
os.path.join(basis, "bilder_04", "titelseite.svg"),
|
||||
os.path.join(basis, "OR_HTML_04", "bilder_04", "titelseite.svg"),
|
||||
]
|
||||
d = datum_string()
|
||||
|
||||
fig = plt.figure(figsize=(8.27, 11.69))
|
||||
ax = fig.add_axes([0, 0, 1, 1])
|
||||
ax.set_xlim(0, BREITE)
|
||||
ax.set_ylim(HOEHE, 0) # invertiert: y zählt von oben (wie SVG)
|
||||
ax.axis("off")
|
||||
|
||||
_design(ax)
|
||||
_texte(ax, d)
|
||||
|
||||
for pfad in ausgaben:
|
||||
os.makedirs(os.path.dirname(pfad), exist_ok=True)
|
||||
fig.savefig(pfad, format="svg", metadata={"Date": None})
|
||||
plt.close(fig)
|
||||
print(f"Titelseite erzeugt ({d}): {', '.join(ausgaben)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
165
bilder_04/erzeuge_var_cvar.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_var_cvar.py
|
||||
"""
|
||||
Erzeugt das VaR/CVaR-Diagramm zum Kapitel CVaR:
|
||||
|
||||
bilder_04/kap_cvar_var_vergleich.svg statisch, fuer das PDF
|
||||
bilder_04/kap_cvar_var_vergleich.png Rasterfassung
|
||||
|
||||
Gezeigt wird die Verteilung simulierter Tagesrenditen mit beiden Kennzahlen:
|
||||
Der VaR ist die SCHWELLE, unter die nur 5 % der Tage fallen; der CVaR ist der
|
||||
MITTELWERT genau dieser 5 %. Der Abstand zwischen beiden Linien ist die
|
||||
Information, die der VaR wegwirft.
|
||||
|
||||
Zur Instanz: Die Renditen stammen aus einer t-Verteilung mit drei
|
||||
Freiheitsgraden, auf Standardabweichung 1 normiert und dann auf eine realistische
|
||||
Tagesvolatilitaet skaliert - dasselbe Fat-Tail-Modell, mit dem Teil 1 von
|
||||
VaR_CVaR_Demo.py rechnet. Die beiden Kennzahlen werden mit den Funktionen aus
|
||||
demselben Programm bestimmt, insbesondere der CVaR ueber Rockafellar-Uryasev
|
||||
statt ueber den naheliegenden, aber falschen Mittelwert aller Werte jenseits des
|
||||
VaR.
|
||||
|
||||
Die Zahlen, die das Skript ausgibt, stehen so auch im Kapiteltext. Wer die
|
||||
Instanz aendert, muss beides nachziehen - deshalb druckt das Skript sie.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_var_cvar.py
|
||||
|
||||
Benoetigt: numpy, scipy, matplotlib
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from scipy import stats
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
INDIGO, AMBER, ROT = "#4338ca", "#b45309", "#be123c"
|
||||
|
||||
TAGE = 20_000 # simulierte Handelstage
|
||||
FREIHEITSGRADE = 3 # Fat-Tail-Modell des Kapitels
|
||||
TAGESVOLA = 0.014 # 1,4 % Tagesschwankung - realistisch fuer ein Aktienportfolio
|
||||
TAGESDRIFT = 0.0003 # leicht positiver Erwartungswert
|
||||
ALPHA = 0.95
|
||||
|
||||
|
||||
# --- Kennzahlen: wortgleich zu VaR_CVaR_Demo.py -----------------------------
|
||||
|
||||
def var_quantil(verluste: np.ndarray, alpha: float = ALPHA) -> float:
|
||||
"""VaR = Quantil der Verlustverteilung (Verluste positiv, Gewinne negativ)."""
|
||||
return float(np.quantile(verluste, alpha))
|
||||
|
||||
|
||||
def cvar_rockafellar(verluste: np.ndarray, alpha: float = ALPHA) -> float:
|
||||
"""CVaR ueber min_gamma { gamma + 1/(1-alpha) * E[max(Verlust - gamma, 0)] }.
|
||||
|
||||
Der naheliegende Weg "Mittelwert aller Werte >= VaR" ist falsch, sobald die
|
||||
Verteilung Atome hat. Hier hat sie keine, aber die Formel bleibt dieselbe
|
||||
wie im Buchprogramm - und nur so ist der Wert im Bild derselbe wie dort.
|
||||
"""
|
||||
kandidaten = np.unique(verluste)
|
||||
return float(min(g + np.mean(np.maximum(verluste - g, 0.0)) / (1.0 - alpha)
|
||||
for g in kandidaten))
|
||||
|
||||
|
||||
def renditen(saat: int = 2026) -> np.ndarray:
|
||||
"""Tagesrenditen aus dem Fat-Tail-Modell des Kapitels."""
|
||||
rng = np.random.default_rng(saat)
|
||||
verteilung = stats.t(df=FREIHEITSGRADE)
|
||||
roh = verteilung.rvs(size=TAGE, random_state=rng) / verteilung.std()
|
||||
return TAGESDRIFT + TAGESVOLA * roh
|
||||
|
||||
|
||||
AUSSCHNITT = 8.0 # Prozent - der sichtbare Bereich der x-Achse
|
||||
|
||||
|
||||
def zeichne(werte: np.ndarray, var: float, cvar: float) -> None:
|
||||
"""Zeichnet die Verteilung mit beiden Kennzahlen.
|
||||
|
||||
Die x-Achse ist bewusst auf +/- 8 % beschnitten. Eine t-Verteilung mit drei
|
||||
Freiheitsgraden hat keine endliche Woelbung: Einzelne Tage liegen bei +70 %,
|
||||
und ungeschnitten quetscht sich die gesamte Verteilung zu einem Strich
|
||||
zusammen. Wie viele Tage ausserhalb liegen, steht im Bild - sie sind nicht
|
||||
verschwiegen, sondern gerade der Punkt.
|
||||
"""
|
||||
figur, achse = plt.subplots(figsize=(8.0, 4.6))
|
||||
prozent = werte * 100
|
||||
sichtbar = prozent[np.abs(prozent) <= AUSSCHNITT]
|
||||
achse.hist(sichtbar, bins=90, color=INDIGO, alpha=0.75,
|
||||
edgecolor="white", linewidth=0.3)
|
||||
achse.set_xlim(-AUSSCHNITT, AUSSCHNITT)
|
||||
|
||||
achse.axvspan(-AUSSCHNITT, -var * 100, color=ROT, alpha=0.08, zorder=0)
|
||||
achse.axvline(-var * 100, color=AMBER, linewidth=2.0, linestyle="--")
|
||||
achse.axvline(-cvar * 100, color=ROT, linewidth=2.0)
|
||||
|
||||
def komma(wert: float, stellen: int = 2) -> str:
|
||||
return f"{wert:.{stellen}f}".replace(".", ",")
|
||||
|
||||
# Platz oberhalb der Verteilung schaffen, damit keine Beschriftung in den
|
||||
# Balken liegt - die Aussage steht sonst buchstaeblich im Weg.
|
||||
hoehe = achse.get_ylim()[1]
|
||||
achse.set_ylim(0, hoehe * 1.30)
|
||||
|
||||
achse.annotate(f"VaR 95 % = {komma(var * 100)} %\ndie Schwelle",
|
||||
(-var * 100, hoehe * 1.24), textcoords="offset points",
|
||||
xytext=(8, 0), fontsize=9, color=AMBER, va="top")
|
||||
achse.annotate(f"CVaR 95 % = {komma(cvar * 100)} %\nder Mittelwert dahinter",
|
||||
(-cvar * 100, hoehe * 1.24), textcoords="offset points",
|
||||
xytext=(-8, 0), fontsize=9, color=ROT, va="top", ha="right")
|
||||
|
||||
anteil = float(np.mean(werte <= -var))
|
||||
achse.annotate(f"die schlechtesten\n{anteil * 100:.0f} % der Tage",
|
||||
((-AUSSCHNITT - var * 100) / 2, hoehe * 0.55),
|
||||
fontsize=8.5, color="#475569", ha="center")
|
||||
|
||||
draussen = int(np.sum(np.abs(prozent) > AUSSCHNITT))
|
||||
schlimmster = float(prozent.min())
|
||||
achse.annotate(f"{draussen} Tage liegen außerhalb dieses Ausschnitts,\n"
|
||||
f"der schlechteste bei {komma(schlimmster, 1)} %.\n"
|
||||
f"Genau sie zählt der VaR nicht mit.",
|
||||
xy=(0.985, 0.80), xycoords="axes fraction", fontsize=8.5,
|
||||
color="#475569", ha="right", va="top")
|
||||
|
||||
achse.set_xlabel("Tagesrendite des Portfolios (%)")
|
||||
achse.set_ylabel("Häufigkeit (von 20 000 Tagen)")
|
||||
achse.set_title("Der VaR ist eine Schwelle, der CVaR ein Mittelwert",
|
||||
fontsize=11)
|
||||
achse.grid(axis="y", linestyle=":", alpha=0.45)
|
||||
for rand in ("top", "right"):
|
||||
achse.spines[rand].set_visible(False)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_cvar_var_vergleich.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160,
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
werte = renditen()
|
||||
verluste = -werte
|
||||
var = var_quantil(verluste)
|
||||
cvar = cvar_rockafellar(verluste)
|
||||
schlimmster = float(verluste.max())
|
||||
print(f"{TAGE} simulierte Tage, t(df={FREIHEITSGRADE}), "
|
||||
f"Tagesvola {TAGESVOLA * 100:.1f} %")
|
||||
print(f"VaR 95 % = {var * 100:.2f} % (Schwelle)")
|
||||
print(f"CVaR 95 % = {cvar * 100:.2f} % (Mittel der schlechtesten 5 %)")
|
||||
print(f"schlimmster Einzeltag: {schlimmster * 100:.2f} %")
|
||||
print(f"Verhaeltnis CVaR/VaR: {cvar / var:.2f}")
|
||||
zeichne(werte, var, cvar)
|
||||
191
bilder_04/erzeuge_vrp_touren.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# erzeuge_vrp_touren.py
|
||||
"""
|
||||
Erzeugt die Tourenkarte zum Graphen-Kapitel:
|
||||
|
||||
bilder_04/kap_graphen_vrp_touren.svg / .png
|
||||
|
||||
Das Vorgaengerbild war ein Schema: zwei Touren, vier Kunden, Zeitfenster in
|
||||
Uhrzeiten. Die Instanz des Kapitels hat 16 Kunden, vier Fahrzeuge und
|
||||
Zeitfenster in Minuten - das Bild zeigte also die Bauform, nicht den Fall.
|
||||
|
||||
Hier wird die Instanz aus VRP_Flotten_Routing.py geloest und die Loesung als
|
||||
Karte gezeichnet: Depot, Kunden (Punktgroesse = Bedarf), die vier Touren in
|
||||
vier Farben, und in der Legende je Fahrzeug Strecke und Auslastung.
|
||||
|
||||
ZUR REPRODUZIERBARKEIT: Das Buchprogramm gibt der Suche ein ZEITBUDGET von
|
||||
fuenf Sekunden. Ein Zeitbudget ist auf einer anderen Maschine ein anderes
|
||||
Rechenbudget, das Ergebnis also nicht garantiert identisch. Fuer das Bild wird
|
||||
deshalb zusaetzlich eine feste Loesungszahl als Abbruchkriterium gesetzt und
|
||||
die Gesamtstrecke ausgegeben - weicht sie vom abgedruckten Lauf ab, faellt es
|
||||
sofort auf, statt still ins Buch zu wandern.
|
||||
|
||||
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||||
python3 bilder_04/erzeuge_vrp_touren.py
|
||||
|
||||
Benoetigt: numpy, matplotlib, ortools
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from ortools.constraint_solver import pywrapcp, routing_enums_pb2
|
||||
|
||||
# Reproduzierbare SVG-Ausgabe (siehe erzeuge_titelseite.py)
|
||||
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
|
||||
|
||||
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BILDER = os.path.join(BASIS, "bilder_04")
|
||||
|
||||
TOURFARBEN = ["#4338ca", "#0891b2", "#b45309", "#059669"]
|
||||
|
||||
# --- Instanz: wortgleich zu VRP_Flotten_Routing.py --------------------------
|
||||
SERVICEZEIT = 10
|
||||
WARTEZEIT_MAX = 60
|
||||
SCHICHTLAENGE = 600
|
||||
SAAT = 42
|
||||
ZEITBUDGET_S = 5
|
||||
LOESUNGSGRENZE = 300 # zusaetzliches, maschinenunabhaengiges Kriterium
|
||||
|
||||
ZEITFENSTER = [
|
||||
(0, SCHICHTLAENGE),
|
||||
(30, 120), (60, 180), (100, 240), (150, 300),
|
||||
(60, 180), (120, 240), (200, 360), (300, 450),
|
||||
(180, 300), (240, 360), (300, 480), (360, 500),
|
||||
(60, 200), (120, 300), (240, 400), (300, 550),
|
||||
]
|
||||
BEDARFE = [0, 2, 3, 1, 4, 2, 2, 3, 1, 2, 4, 3, 2, 1, 2, 3, 2]
|
||||
KAPAZITAETEN = [10, 10, 10, 10]
|
||||
|
||||
|
||||
def erzeuge_daten():
|
||||
anzahl_orte = len(ZEITFENSTER)
|
||||
rng = np.random.default_rng(SAAT)
|
||||
koordinaten = rng.random((anzahl_orte, 2)) * 100
|
||||
distanz = np.zeros((anzahl_orte, anzahl_orte), dtype=int)
|
||||
for i in range(anzahl_orte):
|
||||
for j in range(anzahl_orte):
|
||||
distanz[i][j] = int(np.linalg.norm(koordinaten[i] - koordinaten[j]))
|
||||
return koordinaten, distanz
|
||||
|
||||
|
||||
def loese(distanz):
|
||||
"""CVRPTW mit denselben Einstellungen wie im Buchprogramm."""
|
||||
manager = pywrapcp.RoutingIndexManager(len(distanz), len(KAPAZITAETEN), 0)
|
||||
routing = pywrapcp.RoutingModel(manager)
|
||||
|
||||
def zeit_callback(von_index, nach_index):
|
||||
von = manager.IndexToNode(von_index)
|
||||
nach = manager.IndexToNode(nach_index)
|
||||
service = SERVICEZEIT if von != 0 else 0
|
||||
return int(distanz[von][nach]) + service
|
||||
|
||||
zeit_index = routing.RegisterTransitCallback(zeit_callback)
|
||||
routing.SetArcCostEvaluatorOfAllVehicles(zeit_index)
|
||||
|
||||
def bedarf_callback(von_index):
|
||||
return BEDARFE[manager.IndexToNode(von_index)]
|
||||
|
||||
bedarf_index = routing.RegisterUnaryTransitCallback(bedarf_callback)
|
||||
routing.AddDimensionWithVehicleCapacity(
|
||||
bedarf_index, 0, KAPAZITAETEN, True, "Kapazitaet")
|
||||
|
||||
routing.AddDimension(zeit_index, WARTEZEIT_MAX, SCHICHTLAENGE, False, "Zeit")
|
||||
zeit_dimension = routing.GetDimensionOrDie("Zeit")
|
||||
for ort, (fruehestens, spaetestens) in enumerate(ZEITFENSTER):
|
||||
zeit_dimension.CumulVar(manager.NodeToIndex(ort)).SetRange(
|
||||
fruehestens, spaetestens)
|
||||
|
||||
parameter = pywrapcp.DefaultRoutingSearchParameters()
|
||||
parameter.first_solution_strategy = (
|
||||
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
|
||||
parameter.local_search_metaheuristic = (
|
||||
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
|
||||
parameter.time_limit.seconds = ZEITBUDGET_S
|
||||
parameter.solution_limit = LOESUNGSGRENZE
|
||||
|
||||
loesung = routing.SolveWithParameters(parameter)
|
||||
if not loesung:
|
||||
raise SystemExit("Keine zulaessige Routenfuehrung gefunden.")
|
||||
|
||||
touren = []
|
||||
for fahrzeug in range(len(KAPAZITAETEN)):
|
||||
index = routing.Start(fahrzeug)
|
||||
if routing.IsEnd(loesung.Value(routing.NextVar(index))):
|
||||
continue
|
||||
stationen, fracht, strecke = [], 0, 0
|
||||
while not routing.IsEnd(index):
|
||||
knoten = manager.IndexToNode(index)
|
||||
stationen.append(knoten)
|
||||
fracht += BEDARFE[knoten]
|
||||
vorher = index
|
||||
index = loesung.Value(routing.NextVar(index))
|
||||
strecke += int(distanz[manager.IndexToNode(vorher)][
|
||||
manager.IndexToNode(index)])
|
||||
stationen.append(0)
|
||||
touren.append({"stationen": stationen, "fracht": fracht,
|
||||
"strecke": strecke,
|
||||
"kapazitaet": KAPAZITAETEN[fahrzeug]})
|
||||
return touren
|
||||
|
||||
|
||||
def zeichne(koordinaten, touren) -> None:
|
||||
figur, achse = plt.subplots(figsize=(7.6, 6.2))
|
||||
gesamt = sum(t["strecke"] for t in touren)
|
||||
|
||||
for nummer, (tour, farbe) in enumerate(zip(touren, TOURFARBEN), start=1):
|
||||
weg = koordinaten[tour["stationen"]]
|
||||
achse.plot(weg[:, 0], weg[:, 1], "-", color=farbe, linewidth=1.8,
|
||||
alpha=0.85, zorder=2,
|
||||
label=f"Fahrzeug {nummer}: {tour['strecke']} km, "
|
||||
f"{tour['fracht']}/{tour['kapazitaet']} Einheiten")
|
||||
|
||||
for ort in range(1, len(koordinaten)):
|
||||
achse.scatter(*koordinaten[ort], s=30 + 26 * BEDARFE[ort],
|
||||
color="#334155", zorder=3)
|
||||
achse.annotate(f"K{ort}", koordinaten[ort], textcoords="offset points",
|
||||
xytext=(7, 5), fontsize=7.5, color="#475569")
|
||||
|
||||
achse.scatter(*koordinaten[0], marker="s", s=190, color="#be123c", zorder=4)
|
||||
achse.annotate("Depot", koordinaten[0], textcoords="offset points",
|
||||
xytext=(9, 7), fontsize=9.5, color="#be123c",
|
||||
fontweight="bold")
|
||||
|
||||
achse.set_xlabel("km")
|
||||
achse.set_ylabel("km")
|
||||
achse.set_title(f"Vier Touren für 16 Kunden mit Zeitfenstern — "
|
||||
f"{gesamt} km insgesamt\n"
|
||||
"Punktgröße = Liefermenge", fontsize=10.5)
|
||||
achse.grid(linestyle=":", alpha=0.4)
|
||||
achse.legend(fontsize=8, loc="upper center", bbox_to_anchor=(0.5, -0.11),
|
||||
ncol=2, frameon=False)
|
||||
for seite in ("top", "right"):
|
||||
achse.spines[seite].set_visible(False)
|
||||
figur.tight_layout()
|
||||
|
||||
os.makedirs(BILDER, exist_ok=True)
|
||||
for endung in ("svg", "png"):
|
||||
pfad = os.path.join(BILDER, f"kap_graphen_vrp_touren.{endung}")
|
||||
figur.savefig(pfad, format=endung, dpi=160, bbox_inches="tight",
|
||||
metadata={"Date": None} if endung == "svg" else None)
|
||||
print(f"geschrieben: {pfad}")
|
||||
plt.close(figur)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
koordinaten, distanz = erzeuge_daten()
|
||||
touren = loese(distanz)
|
||||
gesamt = sum(t["strecke"] for t in touren)
|
||||
for nummer, tour in enumerate(touren, start=1):
|
||||
stationen = " -> ".join("Depot" if s == 0 else f"K{s}"
|
||||
for s in tour["stationen"])
|
||||
print(f"Fahrzeug {nummer}: {tour['strecke']:3} km, "
|
||||
f"{tour['fracht']}/{tour['kapazitaet']} | {stationen}")
|
||||
print(f"Gesamtstrecke: {gesamt} km")
|
||||
zeichne(koordinaten, touren)
|
||||
BIN
bilder_04/kap01_analytik_reifegrad.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
48
bilder_04/kap01_analytik_reifegrad.svg
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d01 Pages: 1 -->
|
||||
<svg width="507pt" height="87pt"
|
||||
viewBox="7.00 7.00 500.00 79.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 75.48)">
|
||||
<title>d01</title>
|
||||
<!-- desk -->
|
||||
<g id="node1" class="node">
|
||||
<title>desk</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M117.42,-64.28C117.42,-64.28 12,-64.28 12,-64.28 6,-64.28 0,-58.28 0,-52.28 0,-52.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 117.42,0 117.42,0 123.42,0 129.42,-6 129.42,-12 129.42,-12 129.42,-52.28 129.42,-52.28 129.42,-58.28 123.42,-64.28 117.42,-64.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-44.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">DESKRIPTIV</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Business Intelligence</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">„Was ist passiert?“</text>
|
||||
</g>
|
||||
<!-- pred -->
|
||||
<g id="node2" class="node">
|
||||
<title>pred</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M300.34,-64.28C300.34,-64.28 184.42,-64.28 184.42,-64.28 178.42,-64.28 172.42,-58.28 172.42,-52.28 172.42,-52.28 172.42,-12 172.42,-12 172.42,-6 178.42,0 184.42,0 184.42,0 300.34,0 300.34,0 306.34,0 312.34,-6 312.34,-12 312.34,-12 312.34,-52.28 312.34,-52.28 312.34,-58.28 306.34,-64.28 300.34,-64.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="203.75" y="-44.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">PRÄDIKTIV</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="185.38" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Data Science / ML</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="185.38" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">„Was wird geschehen?“</text>
|
||||
</g>
|
||||
<!-- desk->pred -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>desk->pred</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M130.13,-32.14C139.73,-32.14 149.74,-32.14 159.65,-32.14"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="159.62,-35.29 168.62,-32.14 159.62,-28.99 159.62,-35.29"/>
|
||||
</g>
|
||||
<!-- pres -->
|
||||
<g id="node3" class="node">
|
||||
<title>pres</title>
|
||||
<path fill="#7c3aed" stroke="#7c3aed" stroke-width="2" d="M472.76,-64.28C472.76,-64.28 367.34,-64.28 367.34,-64.28 361.34,-64.28 355.34,-58.28 355.34,-52.28 355.34,-52.28 355.34,-12 355.34,-12 355.34,-6 361.34,0 367.34,0 367.34,0 472.76,0 472.76,0 478.76,0 484.76,-6 484.76,-12 484.76,-12 484.76,-52.28 484.76,-52.28 484.76,-58.28 478.76,-64.28 472.76,-64.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="372.42" y="-44.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">PRÄSKRIPTIV</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="368.3" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">Operations Research</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="368.3" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">„Was sollen wir tun?“</text>
|
||||
</g>
|
||||
<!-- pred->pres -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>pred->pres</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M313.31,-32.14C322.97,-32.14 332.93,-32.14 342.7,-32.14"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="342.46,-35.29 351.46,-32.14 342.46,-28.99 342.46,-35.29"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
bilder_04/kap01_vier_bausteine.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
58
bilder_04/kap01_vier_bausteine.svg
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d02 Pages: 1 -->
|
||||
<svg width="447pt" height="271pt"
|
||||
viewBox="7.00 7.00 439.00 264.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 260.04)">
|
||||
<title>d02</title>
|
||||
<!-- param -->
|
||||
<g id="node1" class="node">
|
||||
<title>param</title>
|
||||
<path fill="#fffbeb" stroke="#d97706" stroke-width="2" d="M292.54,-248.84C292.54,-248.84 128.62,-248.84 128.62,-248.84 122.62,-248.84 116.62,-242.84 116.62,-236.84 116.62,-236.84 116.62,-206.56 116.62,-206.56 116.62,-200.56 122.62,-194.56 128.62,-194.56 128.62,-194.56 292.54,-194.56 292.54,-194.56 298.54,-194.56 304.54,-200.56 304.54,-206.56 304.54,-206.56 304.54,-236.84 304.54,-236.84 304.54,-242.84 298.54,-248.84 292.54,-248.84"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="167.08" y="-228.85" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#d97706">PARAMETER</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="129.58" y="-204.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Konstanten, Daten, Preise, Limits</text>
|
||||
</g>
|
||||
<!-- var -->
|
||||
<g id="node2" class="node">
|
||||
<title>var</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M314.29,-151.56C314.29,-151.56 106.88,-151.56 106.88,-151.56 100.88,-151.56 94.88,-145.56 94.88,-139.56 94.88,-139.56 94.88,-109.28 94.88,-109.28 94.88,-103.28 100.88,-97.28 106.88,-97.28 106.88,-97.28 314.29,-97.28 314.29,-97.28 320.29,-97.28 326.29,-103.28 326.29,-109.28 326.29,-109.28 326.29,-139.56 326.29,-139.56 326.29,-145.56 320.29,-151.56 314.29,-151.56"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="107.83" y="-131.57" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">ENTSCHEIDUNGSVARIABLEN</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="107.83" y="-107.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">x₁, x₂, …, xₙ : Was wird gesteuert?</text>
|
||||
</g>
|
||||
<!-- param->var -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>param->var</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M210.58,-193.65C210.58,-184.6 210.58,-174.32 210.58,-164.53"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="213.74,-164.56 210.59,-155.56 207.44,-164.56 213.74,-164.56"/>
|
||||
</g>
|
||||
<!-- ziel -->
|
||||
<g id="node3" class="node">
|
||||
<title>ziel</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M181.17,-54.28C181.17,-54.28 12,-54.28 12,-54.28 6,-54.28 0,-48.28 0,-42.28 0,-42.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 181.17,0 181.17,0 187.17,0 193.17,-6 193.17,-12 193.17,-12 193.17,-42.28 193.17,-42.28 193.17,-48.28 187.17,-54.28 181.17,-54.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="42.58" y="-34.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#059669">ZIELFUNKTION</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Minimiere Kosten / Maximiere ROI</text>
|
||||
</g>
|
||||
<!-- var->ziel -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>var->ziel</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M178.22,-96.37C165.71,-85.91 151.23,-73.81 137.97,-62.73"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="140.28,-60.55 131.35,-57.2 136.24,-65.39 140.28,-60.55"/>
|
||||
</g>
|
||||
<!-- neben -->
|
||||
<g id="node4" class="node">
|
||||
<title>neben</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M412.17,-54.28C412.17,-54.28 237,-54.28 237,-54.28 231,-54.28 225,-48.28 225,-42.28 225,-42.28 225,-12 225,-12 225,-6 231,0 237,0 237,0 412.17,0 412.17,0 418.17,0 424.17,-6 424.17,-12 424.17,-12 424.17,-42.28 424.17,-42.28 424.17,-48.28 418.17,-54.28 412.17,-54.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="245.46" y="-34.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">NEBENBEDINGUNGEN</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="237.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Harte Schranken: Budget, Kapazität</text>
|
||||
</g>
|
||||
<!-- var->neben -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>var->neben</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M242.95,-96.37C255.46,-85.91 269.94,-73.81 283.2,-62.73"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="284.93,-65.39 289.82,-57.2 280.89,-60.55 284.93,-65.39"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
bilder_04/kap02_konvexitaet.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
1744
bilder_04/kap02_konvexitaet.svg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
bilder_04/kap03_solver_architektur.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
67
bilder_04/kap03_solver_architektur.svg
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d05 Pages: 1 -->
|
||||
<svg width="456pt" height="317pt"
|
||||
viewBox="7.00 7.00 449.00 310.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 305.51)">
|
||||
<title>d05</title>
|
||||
<!-- modell -->
|
||||
<g id="node1" class="node">
|
||||
<title>modell</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M346.17,-294.31C346.17,-294.31 87.75,-294.31 87.75,-294.31 81.75,-294.31 75.75,-288.31 75.75,-282.31 75.75,-282.31 75.75,-242.03 75.75,-242.03 75.75,-236.03 81.75,-230.03 87.75,-230.03 87.75,-230.03 346.17,-230.03 346.17,-230.03 352.17,-230.03 358.17,-236.03 358.17,-242.03 358.17,-242.03 358.17,-282.31 358.17,-282.31 358.17,-288.31 352.17,-294.31 346.17,-294.31"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="88.71" y="-274.32" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">PYTHON-MODELLIERUNGSSCHICHT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="88.71" y="-250.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">scipy.optimize.linprog · Google OR-Tools</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="88.71" y="-240.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">CVXPY · Pyomo / PuLP</text>
|
||||
</g>
|
||||
<!-- engines -->
|
||||
<g id="node2" class="node">
|
||||
<title>engines</title>
|
||||
<polygon fill="#0891b2" stroke="none" points="12.96,-130.14 12.96,-164.64 420.96,-164.64 420.96,-130.14 12.96,-130.14"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="12.96,-130.14 12.96,-164.64 420.96,-164.64 420.96,-130.14 12.96,-130.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="118.34" y="-144.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">SOLVER-ENGINES (C / C++)</text>
|
||||
<polygon fill="#ecfeff" stroke="none" points="12.96,-98.64 12.96,-130.14 161.46,-130.14 161.46,-98.64 12.96,-98.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="12.96,-98.64 12.96,-130.14 161.46,-130.14 161.46,-98.64 12.96,-98.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="47.46" y="-111.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#0891b2">LP · MIP · QP</text>
|
||||
<polygon fill="#ecfeff" stroke="none" points="161.46,-98.64 161.46,-130.14 292.71,-130.14 292.71,-98.64 161.46,-98.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="161.46,-98.64 161.46,-130.14 292.71,-130.14 292.71,-98.64 161.46,-98.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="177.21" y="-111.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#0891b2">Constraint · SAT</text>
|
||||
<polygon fill="#ecfeff" stroke="none" points="292.71,-98.64 292.71,-130.14 420.96,-130.14 420.96,-98.64 292.71,-98.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="292.71,-98.64 292.71,-130.14 420.96,-130.14 420.96,-98.64 292.71,-98.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="301.71" y="-111.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#0891b2">Konvex · QP · NLP</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="12.96,-53.64 12.96,-98.64 161.46,-98.64 161.46,-53.64 12.96,-53.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="12.96,-53.64 12.96,-98.64 161.46,-98.64 161.46,-53.64 12.96,-53.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-79.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">HiGHS</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-65.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(Open Source / Default)</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="161.46,-53.64 161.46,-98.64 292.71,-98.64 292.71,-53.64 161.46,-53.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="161.46,-53.64 161.46,-98.64 292.71,-98.64 292.71,-53.64 161.46,-53.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="170.46" y="-79.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">CP-SAT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="170.46" y="-65.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(Kombinatorik / SAT)</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="292.71,-53.64 292.71,-98.64 420.96,-98.64 420.96,-53.64 292.71,-53.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="292.71,-53.64 292.71,-98.64 420.96,-98.64 420.96,-53.64 292.71,-53.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="312.96" y="-79.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">OSQP / Clarabel</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="318.59" y="-65.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(Konvex / QP)</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="12.96,-8.64 12.96,-53.64 161.46,-53.64 161.46,-8.64 12.96,-8.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="12.96,-8.64 12.96,-53.64 161.46,-53.64 161.46,-8.64 12.96,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">Gurobi / CPLEX / Xpress</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="48.96" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(Kommerziell)</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="161.46,-8.64 161.46,-53.64 292.71,-53.64 292.71,-8.64 161.46,-8.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="161.46,-8.64 161.46,-53.64 292.71,-53.64 292.71,-8.64 161.46,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="176.84" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">SCIP / CBC / GLPK</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="176.84" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(OSS akademisch)</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="292.71,-8.64 292.71,-53.64 420.96,-53.64 420.96,-8.64 292.71,-8.64"/>
|
||||
<polygon fill="none" stroke="#0891b2" points="292.71,-8.64 292.71,-53.64 420.96,-53.64 420.96,-8.64 292.71,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="307.34" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">IPOPT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="307.34" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">(NLP / nichtlinear)</text>
|
||||
</g>
|
||||
<!-- modell->engines -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>modell->engines</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M216.96,-229.34C216.96,-216.51 216.96,-200.95 216.96,-184.91"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="220.11,-185.2 216.96,-176.2 213.81,-185.2 220.11,-185.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="282.96" y="-198.53" font-family="DejaVu Sans" font-size="10.00" fill="#334155">  C-API / FFI / Matrix-Export</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
BIN
bilder_04/kap04_primal_dual.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
40
bilder_04/kap04_primal_dual.svg
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d22 Pages: 1 -->
|
||||
<svg width="359pt" height="248pt"
|
||||
viewBox="7.00 7.00 352.00 241.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 236.51)">
|
||||
<title>d22</title>
|
||||
<!-- primal -->
|
||||
<g id="node1" class="node">
|
||||
<title>primal</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M302.3,-225.31C302.3,-225.31 34.12,-225.31 34.12,-225.31 28.12,-225.31 22.12,-219.31 22.12,-213.31 22.12,-213.31 22.12,-153.03 22.12,-153.03 22.12,-147.03 28.12,-141.03 34.12,-141.03 34.12,-141.03 302.3,-141.03 302.3,-141.03 308.3,-141.03 314.3,-147.03 314.3,-153.03 314.3,-153.03 314.3,-213.31 314.3,-213.31 314.3,-219.31 308.3,-225.31 302.3,-225.31"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="35.09" y="-205.32" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">PRIMALES PROBLEM  (Unternehmer)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="35.09" y="-181.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Maximiere Gewinn aus Produkten</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="35.09" y="-171.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Zielfunktion:   max  cᵀx</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="35.09" y="-161.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Restriktionen:  A x ≤ b</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="35.09" y="-151.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Variablen:  x ≥ 0  (Produktionsmengen)</text>
|
||||
</g>
|
||||
<!-- dual -->
|
||||
<g id="node2" class="node">
|
||||
<title>dual</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M324.42,-84.28C324.42,-84.28 12,-84.28 12,-84.28 6,-84.28 0,-78.28 0,-72.28 0,-72.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 324.42,0 324.42,0 330.42,0 336.42,-6 336.42,-12 336.42,-12 336.42,-72.28 336.42,-72.28 336.42,-78.28 330.42,-84.28 324.42,-84.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-64.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">DUALES PROBLEM  (Ressourcen-Bewerter)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-40.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Minimiere Bewertung der Ressourcen</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-30.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Zielfunktion:   min  bᵀy</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Restriktionen:  Aᵀy ≥ c</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Variablen:  y ≥ 0  (Schattenpreise)</text>
|
||||
</g>
|
||||
<!-- primal->dual -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>primal->dual</title>
|
||||
<path fill="none" stroke="#7c3aed" stroke-width="2.4" d="M168.21,-127.45C168.21,-117.65 168.21,-107.44 168.21,-97.65"/>
|
||||
<polygon fill="#7c3aed" stroke="#7c3aed" stroke-width="2.4" points="165.06,-127.45 168.21,-136.45 171.36,-127.45 165.06,-127.45"/>
|
||||
<polygon fill="#7c3aed" stroke="#7c3aed" stroke-width="2.4" points="171.36,-97.72 168.21,-88.72 165.06,-97.72 171.36,-97.72"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="193.71" y="-109.53" font-family="DejaVu Sans" font-size="10.00" fill="#7c3aed">  Dualität  </text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
BIN
bilder_04/kap06_cp_sat_propagation.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
50
bilder_04/kap06_cp_sat_propagation.svg
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d09 Pages: 1 -->
|
||||
<svg width="393pt" height="321pt"
|
||||
viewBox="7.00 7.00 386.00 314.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 310.04)">
|
||||
<title>d09</title>
|
||||
<!-- store -->
|
||||
<g id="node1" class="node">
|
||||
<title>store</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M290.3,-298.84C290.3,-298.84 80.63,-298.84 80.63,-298.84 74.63,-298.84 68.63,-292.84 68.63,-286.84 68.63,-286.84 68.63,-246.56 68.63,-246.56 68.63,-240.56 74.63,-234.56 80.63,-234.56 80.63,-234.56 290.3,-234.56 290.3,-234.56 296.3,-234.56 302.3,-240.56 302.3,-246.56 302.3,-246.56 302.3,-286.84 302.3,-286.84 302.3,-292.84 296.3,-298.84 290.3,-298.84"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="111.96" y="-278.85" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">CONSTRAINT STORE</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="81.59" y="-254.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Domain x₁ ∈ {1, 2, 3, 4},  x₂ ∈ {2, 3, 4, 5}</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="81.59" y="-244.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Constraint: x₁ + x₂ = 7</text>
|
||||
</g>
|
||||
<!-- prop -->
|
||||
<g id="node2" class="node">
|
||||
<title>prop</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M331.92,-191.56C331.92,-191.56 39,-191.56 39,-191.56 33,-191.56 27,-185.56 27,-179.56 27,-179.56 27,-129.28 27,-129.28 27,-123.28 33,-117.28 39,-117.28 39,-117.28 331.92,-117.28 331.92,-117.28 337.92,-117.28 343.92,-123.28 343.92,-129.28 343.92,-129.28 343.92,-179.56 343.92,-179.56 343.92,-185.56 337.92,-191.56 331.92,-191.56"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="39.96" y="-171.57" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">CONSTRAINT PROPAGATION  (Filterung)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="39.96" y="-147.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• x₁ = 1 ⇒ x₂ = 6  (nicht in Domain!)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="39.96" y="-137.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">  ⟶ entferne 1 aus Domain(x₁)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="39.96" y="-127.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• Reduziert: x₁ ∈ {2, 3, 4}, x₂ ∈ {3, 4, 5}</text>
|
||||
</g>
|
||||
<!-- store->prop -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>store->prop</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M185.46,-233.8C185.46,-224.58 185.46,-214.29 185.46,-204.28"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="188.61,-204.33 185.46,-195.33 182.31,-204.33 188.61,-204.33"/>
|
||||
</g>
|
||||
<!-- sat -->
|
||||
<g id="node3" class="node">
|
||||
<title>sat</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M358.92,-74.28C358.92,-74.28 12,-74.28 12,-74.28 6,-74.28 0,-68.28 0,-62.28 0,-62.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 358.92,0 358.92,0 364.92,0 370.92,-6 370.92,-12 370.92,-12 370.92,-62.28 370.92,-62.28 370.92,-68.28 364.92,-74.28 358.92,-74.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-54.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#7c3aed">SAT-SEARCH & CONFLICT LEARNING  (Nogoods)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-30.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• Bei Widerspruch lernt der SAT-Core eine Klausel</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">  („diese Belegung nie wieder prüfen“)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• Non-chronological Backtracking</text>
|
||||
</g>
|
||||
<!-- prop->sat -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>prop->sat</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M185.46,-116.31C185.46,-106.95 185.46,-96.76 185.46,-86.92"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="188.61,-87.21 185.46,-78.21 182.31,-87.21 188.61,-87.21"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
BIN
bilder_04/kap06_jobshop_gantt.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
1327
bilder_04/kap06_jobshop_gantt.svg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
bilder_04/kap09_unsicherheit_ansaetze.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
45
bilder_04/kap09_unsicherheit_ansaetze.svg
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d13 Pages: 1 -->
|
||||
<svg width="568pt" height="178pt"
|
||||
viewBox="7.00 7.00 561.00 170.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 166.48)">
|
||||
<title>d13</title>
|
||||
<!-- t -->
|
||||
<g id="node1" class="node">
|
||||
<title>t</title>
|
||||
<polygon fill="#7c3aed" stroke="none" points="12.96,-112.14 12.96,-146.64 532.71,-146.64 532.71,-112.14 12.96,-112.14"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="12.96,-112.14 12.96,-146.64 532.71,-146.64 532.71,-112.14 12.96,-112.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="132.96" y="-126.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">ANSÄTZE FÜR UNBEKANNTE ZUKUNFT</text>
|
||||
<polygon fill="#eef2ff" stroke="none" points="12.96,-80.64 12.96,-112.14 168.21,-112.14 168.21,-80.64 12.96,-80.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="12.96,-80.64 12.96,-112.14 168.21,-112.14 168.21,-80.64 12.96,-80.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="46.33" y="-93.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#4338ca">MONTE CARLO</text>
|
||||
<polygon fill="#ecfeff" stroke="none" points="168.21,-80.64 168.21,-112.14 366.96,-112.14 366.96,-80.64 168.21,-80.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="168.21,-80.64 168.21,-112.14 366.96,-112.14 366.96,-80.64 168.21,-80.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="177.21" y="-93.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#0891b2">STOCHASTISCHES RECOURSE</text>
|
||||
<polygon fill="#ecfdf5" stroke="none" points="366.96,-80.64 366.96,-112.14 532.71,-112.14 532.71,-80.64 366.96,-80.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="366.96,-80.64 366.96,-112.14 532.71,-112.14 532.71,-80.64 366.96,-80.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="375.96" y="-93.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#059669">ROBUSTE OPTIMIERUNG</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="12.96,-8.64 12.96,-80.64 168.21,-80.64 168.21,-8.64 12.96,-8.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="12.96,-8.64 12.96,-80.64 168.21,-80.64 168.21,-8.64 12.96,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-61.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Simuliert Szenarien</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-47.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Misst Verlustverteilung</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Reine Evaluierung,</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="36.21" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">  keine Optimierung</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="168.21,-8.64 168.21,-80.64 366.96,-80.64 366.96,-8.64 168.21,-8.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="168.21,-8.64 168.21,-80.64 366.96,-80.64 366.96,-8.64 168.21,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="177.21" y="-61.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Entscheidet Stufe 1</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="177.21" y="-47.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Passt in Stufe 2 an</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="177.21" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Optimiert Erwartungswert</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="197.46" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">  über alle Szenarien</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="366.96,-8.64 366.96,-80.64 532.71,-80.64 532.71,-8.64 366.96,-8.64"/>
|
||||
<polygon fill="none" stroke="#7c3aed" points="366.96,-8.64 366.96,-80.64 532.71,-80.64 532.71,-8.64 366.96,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="375.96" y="-61.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Sucht den Worst-Case</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="375.96" y="-47.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">• Keine Verteilungs-</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="396.96" y="-34.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">  annahme nötig</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
BIN
bilder_04/kap10_zustandskette.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
61
bilder_04/kap10_zustandskette.svg
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d14 Pages: 1 -->
|
||||
<svg width="543pt" height="97pt"
|
||||
viewBox="7.00 7.00 536.00 89.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 85.48)">
|
||||
<title>d14</title>
|
||||
<!-- S0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>S0</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M92.67,-74.28C92.67,-74.28 12,-74.28 12,-74.28 6,-74.28 0,-68.28 0,-62.28 0,-62.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 92.67,0 92.67,0 98.67,0 104.67,-6 104.67,-12 104.67,-12 104.67,-62.28 104.67,-62.28 104.67,-68.28 98.67,-74.28 92.67,-74.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-54.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">Zustand S₀</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-30.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">t = 0</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Aktion a₀</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Kosten C₀</text>
|
||||
</g>
|
||||
<!-- S1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>S1</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M240.34,-74.28C240.34,-74.28 159.67,-74.28 159.67,-74.28 153.67,-74.28 147.67,-68.28 147.67,-62.28 147.67,-62.28 147.67,-12 147.67,-12 147.67,-6 153.67,0 159.67,0 159.67,0 240.34,0 240.34,0 246.34,0 252.34,-6 252.34,-12 252.34,-12 252.34,-62.28 252.34,-62.28 252.34,-68.28 246.34,-74.28 240.34,-74.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="160.63" y="-54.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">Zustand S₁</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="160.63" y="-30.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">t = 1</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="160.63" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Aktion a₁</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="160.63" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Kosten C₁</text>
|
||||
</g>
|
||||
<!-- S0->S1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>S0->S1</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M105.51,-37.14C115.03,-37.14 125.06,-37.14 134.88,-37.14"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="134.67,-40.29 143.67,-37.14 134.67,-33.99 134.67,-40.29"/>
|
||||
</g>
|
||||
<!-- dots -->
|
||||
<g id="node3" class="node">
|
||||
<title>dots</title>
|
||||
<text xml:space="preserve" text-anchor="middle" x="322.34" y="-30.14" font-family="DejaVu Sans" font-size="20.00" fill="#334155">…</text>
|
||||
</g>
|
||||
<!-- S1->dots -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>S1->dots</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M253.09,-37.14C263.45,-37.14 274.13,-37.14 283.89,-37.14"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="283.62,-40.29 292.62,-37.14 283.62,-33.99 283.62,-40.29"/>
|
||||
</g>
|
||||
<!-- ST -->
|
||||
<g id="node4" class="node">
|
||||
<title>ST</title>
|
||||
<path fill="#059669" stroke="#059669" stroke-width="2" d="M509.01,-69.28C509.01,-69.28 404.34,-69.28 404.34,-69.28 398.34,-69.28 392.34,-63.28 392.34,-57.28 392.34,-57.28 392.34,-17 392.34,-17 392.34,-11 398.34,-5 404.34,-5 404.34,-5 509.01,-5 509.01,-5 515.01,-5 521.01,-11 521.01,-17 521.01,-17 521.01,-57.28 521.01,-57.28 521.01,-63.28 515.01,-69.28 509.01,-69.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="405.3" y="-49.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">Endzustand Sₜ</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="405.3" y="-25.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">t = T</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="405.3" y="-15.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">alles verkauft</text>
|
||||
</g>
|
||||
<!-- dots->ST -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>dots->ST</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M349.17,-37.14C358.12,-37.14 368.66,-37.14 379.57,-37.14"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="379.47,-40.29 388.47,-37.14 379.47,-33.99 379.47,-40.29"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
bilder_04/kap11_ledoit_wolf_shrinkage.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
61
bilder_04/kap11_ledoit_wolf_shrinkage.svg
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d15 Pages: 1 -->
|
||||
<svg width="425pt" height="301pt"
|
||||
viewBox="7.00 7.00 417.00 294.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 290.04)">
|
||||
<title>d15</title>
|
||||
<!-- S0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>S0</title>
|
||||
<path fill="#fff1f3" stroke="#e11d48" stroke-width="2" d="M302.29,-278.84C302.29,-278.84 88.88,-278.84 88.88,-278.84 82.88,-278.84 76.87,-272.84 76.87,-266.84 76.87,-266.84 76.87,-206.56 76.87,-206.56 76.87,-200.56 82.87,-194.56 88.87,-194.56 88.87,-194.56 302.29,-194.56 302.29,-194.56 308.29,-194.56 314.29,-200.56 314.29,-206.56 314.29,-206.56 314.29,-266.84 314.29,-266.84 314.29,-272.84 308.29,-278.84 302.29,-278.84"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="89.83" y="-258.85" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#e11d48">STICHPROBEN-KOVARIANZ  S</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="89.83" y="-234.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• T < N: singulär (Rang < N)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="89.83" y="-224.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• T ≈ N: instabil, Eigenwerte streuen</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="89.83" y="-214.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">• kleine Eigenwerte unterschätzt</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="89.83" y="-204.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">  ⟶ Solver nutzt Scheinkorrelationen</text>
|
||||
</g>
|
||||
<!-- LW -->
|
||||
<g id="node2" class="node">
|
||||
<title>LW</title>
|
||||
<path fill="#059669" stroke="#059669" stroke-width="2" d="M315.79,-151.56C315.79,-151.56 75.38,-151.56 75.38,-151.56 69.38,-151.56 63.37,-145.56 63.37,-139.56 63.37,-139.56 63.37,-109.28 63.37,-109.28 63.37,-103.28 69.37,-97.28 75.37,-97.28 75.37,-97.28 315.79,-97.28 315.79,-97.28 321.79,-97.28 327.79,-103.28 327.79,-109.28 327.79,-109.28 327.79,-139.56 327.79,-139.56 327.79,-145.56 321.79,-151.56 315.79,-151.56"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="76.33" y="-131.57" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">LEDOIT-WOLF SHRINKAGE  Σ(LW)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="76.33" y="-107.42" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">Σ(LW) = (1 − δ) · S  +  δ · F</text>
|
||||
</g>
|
||||
<!-- S0->LW -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>S0->LW</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M195.58,-193.69C195.58,-184.04 195.58,-173.82 195.58,-164.31"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="198.74,-164.35 195.59,-155.35 192.44,-164.35 198.74,-164.35"/>
|
||||
</g>
|
||||
<!-- S -->
|
||||
<g id="node3" class="node">
|
||||
<title>S</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M163.17,-54.28C163.17,-54.28 12,-54.28 12,-54.28 6,-54.28 0,-48.28 0,-42.28 0,-42.28 0,-12 0,-12 0,-6 6,0 12,0 12,0 163.17,0 163.17,0 169.17,0 175.17,-6 175.17,-12 175.17,-12 175.17,-42.28 175.17,-42.28 175.17,-48.28 169.17,-54.28 163.17,-54.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-34.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">Stichprobenmatrix S</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">unverzerrt, hohes Rauschen</text>
|
||||
</g>
|
||||
<!-- LW->S -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>LW->S</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M164.93,-96.37C153.18,-86.01 139.62,-74.04 127.16,-63.05"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="129.42,-60.85 120.59,-57.26 125.26,-65.58 129.42,-60.85"/>
|
||||
</g>
|
||||
<!-- F -->
|
||||
<g id="node4" class="node">
|
||||
<title>F</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M390.29,-54.28C390.29,-54.28 218.88,-54.28 218.88,-54.28 212.88,-54.28 206.88,-48.28 206.88,-42.28 206.88,-42.28 206.88,-12 206.88,-12 206.88,-6 212.88,0 218.88,0 218.88,0 390.29,0 390.29,0 396.29,0 402.29,-6 402.29,-12 402.29,-12 402.29,-42.28 402.29,-42.28 402.29,-48.28 396.29,-54.28 390.29,-54.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="219.83" y="-34.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">Strukturiertes Target F</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="219.83" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">verzerrt, kein Rauschen</text>
|
||||
</g>
|
||||
<!-- LW->F -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>LW->F</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M226.53,-96.37C238.38,-86.01 252.07,-74.04 264.65,-63.05"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="266.58,-65.54 271.29,-57.25 262.44,-60.8 266.58,-65.54"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
BIN
bilder_04/kap12_efficient_frontier.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
2255
bilder_04/kap12_efficient_frontier.svg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
bilder_04/kap13_cvar_zielfunktion.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
1600
bilder_04/kap13_cvar_zielfunktion.svg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
bilder_04/kap14_engine_overview.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
96
bilder_04/kap14_engine_overview.svg
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d19 Pages: 1 -->
|
||||
<svg width="503pt" height="516pt"
|
||||
viewBox="7.00 7.00 495.00 509.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 504.6)">
|
||||
<title>d19</title>
|
||||
<!-- s1 -->
|
||||
<g id="node1" class="node">
|
||||
<title>s1</title>
|
||||
<path fill="#fffbeb" stroke="#d97706" stroke-width="2" d="M370.67,-493.4C370.67,-493.4 128,-493.4 128,-493.4 122,-493.4 116,-487.4 116,-481.4 116,-481.4 116,-441.12 116,-441.12 116,-435.12 122,-429.12 128,-429.12 128,-429.12 370.67,-429.12 370.67,-429.12 376.67,-429.12 382.67,-435.12 382.67,-441.12 382.67,-441.12 382.67,-481.4 382.67,-481.4 382.67,-487.4 376.67,-493.4 370.67,-493.4"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="128.96" y="-473.41" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#d97706">1 · HISTORISCHE DATENPIPELINE</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="128.96" y="-449.26" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Automatischer Download bereinigter</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="128.96" y="-439.26" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Kursdaten über 5 Jahre</text>
|
||||
</g>
|
||||
<!-- s2 -->
|
||||
<g id="node2" class="node">
|
||||
<title>s2</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M230.67,-386.12C230.67,-386.12 12,-386.12 12,-386.12 6,-386.12 0,-380.12 0,-374.12 0,-374.12 0,-333.84 0,-333.84 0,-327.84 6,-321.84 12,-321.84 12,-321.84 230.67,-321.84 230.67,-321.84 236.67,-321.84 242.67,-327.84 242.67,-333.84 242.67,-333.84 242.67,-374.12 242.67,-374.12 242.67,-380.12 236.67,-386.12 230.67,-386.12"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-366.13" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">2 · FAKTOR-SCORING-MODELL</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-341.98" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">12−1M Momentum</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-331.98" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">als Rendite-Signal μₜ</text>
|
||||
</g>
|
||||
<!-- s1->s2 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>s1->s2</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M210.58,-428.39C197.67,-417.77 183.12,-405.8 169.58,-394.66"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="171.69,-392.32 162.73,-389.03 167.68,-397.18 171.69,-392.32"/>
|
||||
</g>
|
||||
<!-- s3 -->
|
||||
<g id="node3" class="node">
|
||||
<title>s3</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M468.29,-386.12C468.29,-386.12 286.38,-386.12 286.38,-386.12 280.38,-386.12 274.38,-380.12 274.38,-374.12 274.38,-374.12 274.38,-333.84 274.38,-333.84 274.38,-327.84 280.38,-321.84 286.38,-321.84 286.38,-321.84 468.29,-321.84 468.29,-321.84 474.29,-321.84 480.29,-327.84 480.29,-333.84 480.29,-333.84 480.29,-374.12 480.29,-374.12 480.29,-380.12 474.29,-386.12 468.29,-386.12"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="287.33" y="-366.13" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">3 · ROBUSTE KOVARIANZ</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="287.33" y="-341.98" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Ledoit-Wolf-Shrinkage auf</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="287.33" y="-331.98" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">rollierendem 252-Tage-Fenster</text>
|
||||
</g>
|
||||
<!-- s1->s3 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>s1->s3</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M288.09,-428.39C301,-417.77 315.55,-405.8 329.09,-394.66"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="330.99,-397.18 335.94,-389.03 326.98,-392.32 330.99,-397.18"/>
|
||||
</g>
|
||||
<!-- s4 -->
|
||||
<g id="node4" class="node">
|
||||
<title>s4</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M383.42,-278.84C383.42,-278.84 115.25,-278.84 115.25,-278.84 109.25,-278.84 103.25,-272.84 103.25,-266.84 103.25,-266.84 103.25,-226.56 103.25,-226.56 103.25,-220.56 109.25,-214.56 115.25,-214.56 115.25,-214.56 383.42,-214.56 383.42,-214.56 389.42,-214.56 395.42,-220.56 395.42,-226.56 395.42,-226.56 395.42,-266.84 395.42,-266.84 395.42,-272.84 389.42,-278.84 383.42,-278.84"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="116.21" y="-258.85" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#7c3aed">4 · OR-OPTIMIERUNGSKERN (CVXPY)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="116.21" y="-234.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Max. Momentum − CVaR − Reibung</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="116.21" y="-224.7" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Hard Constraints: Sektor-Caps, No-Short</text>
|
||||
</g>
|
||||
<!-- s2->s4 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>s2->s4</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M160.09,-321.11C173,-310.49 187.55,-298.52 201.09,-287.38"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="202.99,-289.9 207.94,-281.75 198.98,-285.04 202.99,-289.9"/>
|
||||
</g>
|
||||
<!-- s3->s4 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>s3->s4</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M338.58,-321.11C325.67,-310.49 311.12,-298.52 297.58,-287.38"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="299.69,-285.04 290.73,-281.75 295.68,-289.9 299.69,-285.04"/>
|
||||
</g>
|
||||
<!-- s5 -->
|
||||
<g id="node5" class="node">
|
||||
<title>s5</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M393.54,-171.56C393.54,-171.56 105.12,-171.56 105.12,-171.56 99.12,-171.56 93.12,-165.56 93.12,-159.56 93.12,-159.56 93.12,-119.28 93.12,-119.28 93.12,-113.28 99.12,-107.28 105.12,-107.28 105.12,-107.28 393.54,-107.28 393.54,-107.28 399.54,-107.28 405.54,-113.28 405.54,-119.28 405.54,-119.28 405.54,-159.56 405.54,-159.56 405.54,-165.56 399.54,-171.56 393.54,-171.56"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="106.08" y="-151.57" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">5 · REBALANCING & ORDER-EXECUTION</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="106.08" y="-127.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Monatliche Neugewichtung mit</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="106.08" y="-117.42" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">realer Transaktionskosten-Verbuchung</text>
|
||||
</g>
|
||||
<!-- s4->s5 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>s4->s5</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M249.33,-213.83C249.33,-204.53 249.33,-194.21 249.33,-184.31"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="252.49,-184.58 249.34,-175.58 246.19,-184.58 252.49,-184.58"/>
|
||||
</g>
|
||||
<!-- s6 -->
|
||||
<g id="node6" class="node">
|
||||
<title>s6</title>
|
||||
<path fill="#059669" stroke="#059669" stroke-width="2" d="M388.29,-64.28C388.29,-64.28 110.38,-64.28 110.38,-64.28 104.38,-64.28 98.37,-58.28 98.37,-52.28 98.37,-52.28 98.37,-12 98.37,-12 98.37,-6 104.37,0 110.37,0 110.37,0 388.29,0 388.29,0 394.29,0 400.29,-6 400.29,-12 400.29,-12 400.29,-52.28 400.29,-52.28 400.29,-58.28 394.29,-64.28 388.29,-64.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="111.33" y="-44.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">6 · LEISTUNGSREPORT & BENCHMARK</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="111.33" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">Sharpe, Max Drawdown, CAGR</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="111.33" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#ffffff">vs. S&P 500 (SPY)</text>
|
||||
</g>
|
||||
<!-- s5->s6 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>s5->s6</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M249.33,-106.55C249.33,-97.25 249.33,-86.93 249.33,-77.03"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="252.49,-77.3 249.34,-68.3 246.19,-77.3 252.49,-77.3"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
BIN
bilder_04/kap15_or_plattform.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
81
bilder_04/kap15_or_plattform.svg
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d18 Pages: 1 -->
|
||||
<svg width="350pt" height="581pt"
|
||||
viewBox="7.00 7.00 343.00 574.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 569.6)">
|
||||
<title>d18</title>
|
||||
<!-- s0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>s0</title>
|
||||
<path fill="#fffbeb" stroke="#d97706" stroke-width="2" d="M225.42,-558.4C225.42,-558.4 69.75,-558.4 69.75,-558.4 63.75,-558.4 57.75,-552.4 57.75,-546.4 57.75,-546.4 57.75,-506.12 57.75,-506.12 57.75,-500.12 63.75,-494.12 69.75,-494.12 69.75,-494.12 225.42,-494.12 225.42,-494.12 231.42,-494.12 237.42,-500.12 237.42,-506.12 237.42,-506.12 237.42,-546.4 237.42,-546.4 237.42,-552.4 231.42,-558.4 225.42,-558.4"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="87.59" y="-538.41" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#d97706">QUELL-SYSTEME</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="70.71" y="-514.26" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">ERP / CRM / HR-Kalender</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="70.71" y="-504.26" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Marktdaten-Feeds / Broker-APIs</text>
|
||||
</g>
|
||||
<!-- s1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>s1</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M255.42,-437.37C255.42,-437.37 39.75,-437.37 39.75,-437.37 33.75,-437.37 27.75,-431.37 27.75,-425.37 27.75,-425.37 27.75,-385.09 27.75,-385.09 27.75,-379.09 33.75,-373.09 39.75,-373.09 39.75,-373.09 255.42,-373.09 255.42,-373.09 261.42,-373.09 267.42,-379.09 267.42,-385.09 267.42,-385.09 267.42,-425.37 267.42,-425.37 267.42,-431.37 261.42,-437.37 255.42,-437.37"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="40.71" y="-417.38" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#0891b2">DATA QUALITY & SNAPSHOTS</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="40.71" y="-393.23" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Validierung (Pydantic / Pandera)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="40.71" y="-383.23" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Unveränderlicher Planungs-Snapshot</text>
|
||||
</g>
|
||||
<!-- s0->s1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>s0->s1</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M147.59,-493.36C147.59,-480.02 147.59,-464.3 147.59,-449.91"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="150.74,-450.24 147.59,-441.24 144.44,-450.24 150.74,-450.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="204.96" y="-462.62" font-family="DejaVu Sans" font-size="10.00" fill="#334155">  Events / REST / Polling</text>
|
||||
</g>
|
||||
<!-- s2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>s2</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M267.42,-316.34C267.42,-316.34 27.75,-316.34 27.75,-316.34 21.75,-316.34 15.75,-310.34 15.75,-304.34 15.75,-304.34 15.75,-254.06 15.75,-254.06 15.75,-248.06 21.75,-242.06 27.75,-242.06 27.75,-242.06 267.42,-242.06 267.42,-242.06 273.42,-242.06 279.42,-248.06 279.42,-254.06 279.42,-254.06 279.42,-304.34 279.42,-304.34 279.42,-310.34 273.42,-316.34 267.42,-316.34"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="28.71" y="-296.35" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#4338ca">OPTIMIERUNGS-WORKER (async)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="28.71" y="-272.2" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Queue (Celery / Redis / Dramatiq)</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="28.71" y="-262.2" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Solver: OR-Tools / HiGHS / CVXPY</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="28.71" y="-252.2" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Timeouts, Fallbacks, Diagnose</text>
|
||||
</g>
|
||||
<!-- s1->s2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>s1->s2</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M147.59,-372.29C147.59,-359.17 147.59,-343.68 147.59,-329.18"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="150.74,-329.34 147.59,-320.34 144.44,-329.34 150.74,-329.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="183.59" y="-341.59" font-family="DejaVu Sans" font-size="10.00" fill="#334155">  JSON-Payload</text>
|
||||
</g>
|
||||
<!-- s3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>s3</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M283.17,-185.31C283.17,-185.31 12,-185.31 12,-185.31 6,-185.31 0,-179.31 0,-173.31 0,-173.31 0,-133.03 0,-133.03 0,-127.03 6,-121.03 12,-121.03 12,-121.03 283.17,-121.03 283.17,-121.03 289.17,-121.03 295.17,-127.03 295.17,-133.03 295.17,-133.03 295.17,-173.31 295.17,-173.31 295.17,-179.31 289.17,-185.31 283.17,-185.31"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-165.32" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#7c3aed">DISPOSITION & HUMAN-IN-THE-LOOP</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-141.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Web-UI / Dashboard</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="12.96" y="-131.17" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Freigabeprozess (Mensch bestätigt)</text>
|
||||
</g>
|
||||
<!-- s2->s3 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>s2->s3</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M147.59,-241.32C147.59,-227.74 147.59,-212.21 147.59,-198.07"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="150.74,-198.1 147.59,-189.1 144.44,-198.1 150.74,-198.1"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="237.59" y="-210.56" font-family="DejaVu Sans" font-size="10.00" fill="#334155">  Ergebnis + Kostenzerlegung + Logs</text>
|
||||
</g>
|
||||
<!-- s4 -->
|
||||
<g id="node5" class="node">
|
||||
<title>s4</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M243.79,-64.28C243.79,-64.28 51.38,-64.28 51.38,-64.28 45.38,-64.28 39.38,-58.28 39.38,-52.28 39.38,-52.28 39.38,-12 39.38,-12 39.38,-6 45.38,0 51.38,0 51.38,0 243.79,0 243.79,0 249.79,0 255.79,-6 255.79,-12 255.79,-12 255.79,-52.28 255.79,-52.28 255.79,-58.28 249.79,-64.28 243.79,-64.28"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="59.84" y="-44.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#059669">AUSFÜHRUNGSSCHICHT</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="52.34" y="-20.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Kalender-Sync / Push-Benachrichtigung</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="52.34" y="-10.14" font-family="DejaVu Sans" font-size="10.00" fill="#1a1a2e">Broker-Order-Routing</text>
|
||||
</g>
|
||||
<!-- s3->s4 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>s3->s4</title>
|
||||
<path fill="none" stroke="#334155" stroke-width="2" d="M147.59,-120.27C147.59,-106.93 147.59,-91.21 147.59,-76.82"/>
|
||||
<polygon fill="#334155" stroke="#334155" stroke-width="2" points="150.74,-77.15 147.59,-68.15 144.44,-77.15 150.74,-77.15"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="174.96" y="-89.53" font-family="DejaVu Sans" font-size="10.00" fill="#334155">  Audit-Trail</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
BIN
bilder_04/kap15_praxisfallen.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
49
bilder_04/kap15_praxisfallen.svg
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Title: d17 Pages: 1 -->
|
||||
<svg width="654pt" height="232pt"
|
||||
viewBox="7.00 7.00 646.00 224.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(11.2 220.48)">
|
||||
<title>d17</title>
|
||||
<!-- t -->
|
||||
<g id="node1" class="node">
|
||||
<title>t</title>
|
||||
<polygon fill="#1a1a2e" stroke="none" points="12.96,-166.14 12.96,-200.64 618.21,-200.64 618.21,-166.14 12.96,-166.14"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-166.14 12.96,-200.64 618.21,-200.64 618.21,-166.14 12.96,-166.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="191.83" y="-180.29" font-family="DejaVu Sans" font-weight="bold" font-size="13.00" fill="#ffffff">DIE 5 FALLSTRICKE IN DER PRAXIS</text>
|
||||
<polygon fill="#fff1f3" stroke="none" points="12.96,-134.64 12.96,-166.14 230.46,-166.14 230.46,-134.64 12.96,-134.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-134.64 12.96,-166.14 230.46,-166.14 230.46,-134.64 12.96,-134.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-147.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#e11d48">1.  INFEASIBILITY-FALLE</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="230.46,-134.64 230.46,-166.14 618.21,-166.14 618.21,-134.64 230.46,-134.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="230.46,-134.64 230.46,-166.14 618.21,-166.14 618.21,-134.64 230.46,-134.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="239.46" y="-146.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">Unlösbare Modelle durch starre, widersprüchliche Hard Constraints</text>
|
||||
<polygon fill="#fffbeb" stroke="none" points="12.96,-103.14 12.96,-134.64 230.46,-134.64 230.46,-103.14 12.96,-103.14"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-103.14 12.96,-134.64 230.46,-134.64 230.46,-103.14 12.96,-103.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-116.19" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#d97706">2.  ERKLÄRBARKEITS-LÜCKE</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="230.46,-103.14 230.46,-134.64 618.21,-134.64 618.21,-103.14 230.46,-103.14"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="230.46,-103.14 230.46,-134.64 618.21,-134.64 618.21,-103.14 230.46,-103.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="239.46" y="-115.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">Mangelnde Akzeptanz wegen fehlender Begründungen (Black-Box)</text>
|
||||
<polygon fill="#f5f3ff" stroke="none" points="12.96,-71.64 12.96,-103.14 230.46,-103.14 230.46,-71.64 12.96,-71.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-71.64 12.96,-103.14 230.46,-103.14 230.46,-71.64 12.96,-71.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-84.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#7c3aed">3.  OVERFITTING / REGIME-SHIFT</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="230.46,-71.64 230.46,-103.14 618.21,-103.14 618.21,-71.64 230.46,-71.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="230.46,-71.64 230.46,-103.14 618.21,-103.14 618.21,-71.64 230.46,-71.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="239.46" y="-83.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">Perfekt auf die Vergangenheit optimiert – scheitert in Krisen</text>
|
||||
<polygon fill="#ecfeff" stroke="none" points="12.96,-40.14 12.96,-71.64 230.46,-71.64 230.46,-40.14 12.96,-40.14"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-40.14 12.96,-71.64 230.46,-71.64 230.46,-40.14 12.96,-40.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-53.19" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#0891b2">4.  DATENVERZUG / LOOKAHEAD</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="230.46,-40.14 230.46,-71.64 618.21,-71.64 618.21,-40.14 230.46,-40.14"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="230.46,-40.14 230.46,-71.64 618.21,-71.64 618.21,-40.14 230.46,-40.14"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="239.46" y="-52.19" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">Signale nutzen Daten, die zum Handlungszeitpunkt fehlten</text>
|
||||
<polygon fill="#eef2ff" stroke="none" points="12.96,-8.64 12.96,-40.14 230.46,-40.14 230.46,-8.64 12.96,-8.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="12.96,-8.64 12.96,-40.14 230.46,-40.14 230.46,-8.64 12.96,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="21.96" y="-21.69" font-family="DejaVu Sans" font-weight="bold" font-size="11.00" fill="#4338ca">5.  LAUFZEIT-EXPLOSION</text>
|
||||
<polygon fill="#ffffff" stroke="none" points="230.46,-8.64 230.46,-40.14 618.21,-40.14 618.21,-8.64 230.46,-8.64"/>
|
||||
<polygon fill="none" stroke="#1a1a2e" points="230.46,-8.64 230.46,-40.14 618.21,-40.14 618.21,-8.64 230.46,-8.64"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="239.46" y="-20.69" font-family="DejaVu Sans" font-size="11.00" fill="#1a1a2e">NP-schwere Modelle verfehlen reale Timeouts bei Wachstum</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
BIN
bilder_04/kap_cvar_var_vergleich.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
2609
bilder_04/kap_cvar_var_vergleich.svg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
bilder_04/kap_fundament_kondition.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
2601
bilder_04/kap_fundament_kondition.svg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
bilder_04/kap_fundament_polyeder.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
2108
bilder_04/kap_fundament_polyeder.svg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
bilder_04/kap_graphen_min_cost_flow.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
95
bilder_04/kap_graphen_min_cost_flow.svg
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="633pt" height="201pt"
|
||||
viewBox="0.00 0.00 633.00 201.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 196.84)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="312.56" y="-177.44" font-family="DejaVu Sans" font-size="12.00">Kostenminimaler Fluss: 140 € für 30 Einheiten</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="312.56" y="-163.19" font-family="DejaVu Sans" font-size="12.00">Kantenbeschriftung: Fluss / Kapazität, Kosten je Einheit</text>
|
||||
<!-- Werk_A -->
|
||||
<g id="node1" class="node">
|
||||
<title>Werk_A</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M87.54,-156.34C87.54,-156.34 12,-156.34 12,-156.34 6,-156.34 0,-150.34 0,-144.34 0,-144.34 0,-117.13 0,-117.13 0,-111.13 6,-105.13 12,-105.13 12,-105.13 87.54,-105.13 87.54,-105.13 93.54,-105.13 99.54,-111.13 99.54,-117.13 99.54,-117.13 99.54,-144.34 99.54,-144.34 99.54,-150.34 93.54,-156.34 87.54,-156.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-140.36" font-family="DejaVu Sans" font-size="10.00">Werk A</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-127.61" font-family="DejaVu Sans" font-size="10.00">Angebot +20</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-114.86" font-family="DejaVu Sans" font-size="10.00">Knotenpreis 4 €</text>
|
||||
</g>
|
||||
<!-- Umschlag -->
|
||||
<g id="node3" class="node">
|
||||
<title>Umschlag</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M375.83,-83.34C375.83,-83.34 300.29,-83.34 300.29,-83.34 294.29,-83.34 288.29,-77.34 288.29,-71.34 288.29,-71.34 288.29,-44.13 288.29,-44.13 288.29,-38.13 294.29,-32.13 300.29,-32.13 300.29,-32.13 375.83,-32.13 375.83,-32.13 381.83,-32.13 387.83,-38.13 387.83,-44.13 387.83,-44.13 387.83,-71.34 387.83,-71.34 387.83,-77.34 381.83,-83.34 375.83,-83.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.06" y="-67.36" font-family="DejaVu Sans" font-size="10.00">Umschlag</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.06" y="-54.61" font-family="DejaVu Sans" font-size="10.00">Durchleitung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.06" y="-41.86" font-family="DejaVu Sans" font-size="10.00">Knotenpreis 0 €</text>
|
||||
</g>
|
||||
<!-- Werk_A->Umschlag -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>Werk_A->Umschlag</title>
|
||||
<path fill="none" stroke="#b45309" stroke-width="4" d="M100.31,-118.1C147.88,-105.97 220.02,-87.58 272.16,-74.29"/>
|
||||
<polygon fill="#b45309" stroke="#b45309" stroke-width="4" points="272.95,-77.69 281.78,-71.83 271.23,-70.91 272.95,-77.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="193.91" y="-109.82" font-family="DejaVu Sans" font-size="9.00" fill="#b45309">15 / 15, 2 €  ⟵ voll</text>
|
||||
</g>
|
||||
<!-- Kunde_1 -->
|
||||
<g id="node4" class="node">
|
||||
<title>Kunde_1</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M613.12,-156.34C613.12,-156.34 533.83,-156.34 533.83,-156.34 527.83,-156.34 521.83,-150.34 521.83,-144.34 521.83,-144.34 521.83,-117.13 521.83,-117.13 521.83,-111.13 527.83,-105.13 533.83,-105.13 533.83,-105.13 613.12,-105.13 613.12,-105.13 619.12,-105.13 625.12,-111.13 625.12,-117.13 625.12,-117.13 625.12,-144.34 625.12,-144.34 625.12,-150.34 619.12,-156.34 613.12,-156.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-140.36" font-family="DejaVu Sans" font-size="10.00">Kunde 1</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-127.61" font-family="DejaVu Sans" font-size="10.00">Bedarf -15</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-114.86" font-family="DejaVu Sans" font-size="10.00">Knotenpreis -1 €</text>
|
||||
</g>
|
||||
<!-- Werk_A->Kunde_1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>Werk_A->Kunde_1</title>
|
||||
<path fill="none" stroke="#0891b2" stroke-width="2.5" d="M100.32,-130.74C195.01,-130.74 401.63,-130.74 507.34,-130.74"/>
|
||||
<polygon fill="#0891b2" stroke="#0891b2" stroke-width="2.5" points="507.17,-134.24 517.17,-130.74 507.17,-127.24 507.17,-134.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.06" y="-133.44" font-family="DejaVu Sans" font-size="9.00" fill="#334155">5 / 10, 5 €</text>
|
||||
</g>
|
||||
<!-- Werk_B -->
|
||||
<g id="node2" class="node">
|
||||
<title>Werk_B</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M87.54,-57.34C87.54,-57.34 12,-57.34 12,-57.34 6,-57.34 0,-51.34 0,-45.34 0,-45.34 0,-18.13 0,-18.13 0,-12.13 6,-6.13 12,-6.13 12,-6.13 87.54,-6.13 87.54,-6.13 93.54,-6.13 99.54,-12.13 99.54,-18.13 99.54,-18.13 99.54,-45.34 99.54,-45.34 99.54,-51.34 93.54,-57.34 87.54,-57.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-41.36" font-family="DejaVu Sans" font-size="10.00">Werk B</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-28.61" font-family="DejaVu Sans" font-size="10.00">Angebot +10</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="49.77" y="-15.86" font-family="DejaVu Sans" font-size="10.00">Knotenpreis 4 €</text>
|
||||
</g>
|
||||
<!-- Werk_B->Umschlag -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>Werk_B->Umschlag</title>
|
||||
<path fill="none" stroke="#cbd5e1" stroke-dasharray="5,2" d="M100.31,-36.24C149.22,-40.68 224.08,-47.48 276.49,-52.24"/>
|
||||
<polygon fill="#cbd5e1" stroke="#cbd5e1" points="275.86,-55.69 286.14,-53.11 276.5,-48.72 275.86,-55.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="193.91" y="-52.03" font-family="DejaVu Sans" font-size="9.00" fill="#94a3b8">0 / 10, 4 €  (ungenutzt)</text>
|
||||
</g>
|
||||
<!-- Kunde_2 -->
|
||||
<g id="node5" class="node">
|
||||
<title>Kunde_2</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M613.12,-57.34C613.12,-57.34 533.83,-57.34 533.83,-57.34 527.83,-57.34 521.83,-51.34 521.83,-45.34 521.83,-45.34 521.83,-18.13 521.83,-18.13 521.83,-12.13 527.83,-6.13 533.83,-6.13 533.83,-6.13 613.12,-6.13 613.12,-6.13 619.12,-6.13 625.12,-12.13 625.12,-18.13 625.12,-18.13 625.12,-45.34 625.12,-45.34 625.12,-51.34 619.12,-57.34 613.12,-57.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-41.36" font-family="DejaVu Sans" font-size="10.00">Kunde 2</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-28.61" font-family="DejaVu Sans" font-size="10.00">Bedarf -15</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="573.47" y="-15.86" font-family="DejaVu Sans" font-size="10.00">Knotenpreis -3 €</text>
|
||||
</g>
|
||||
<!-- Werk_B->Kunde_2 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>Werk_B->Kunde_2</title>
|
||||
<path fill="none" stroke="#b45309" stroke-width="4" d="M100.44,-22.29C166.16,-10.96 285.65,5.56 387.83,-2.49 427.11,-5.58 470.66,-12.32 505.6,-18.55"/>
|
||||
<polygon fill="#b45309" stroke="#b45309" stroke-width="4" points="504.58,-21.92 515.04,-20.27 505.84,-15.03 504.58,-21.92"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.06" y="-5.19" font-family="DejaVu Sans" font-size="9.00" fill="#b45309">10 / 10, 6 €  ⟵ voll</text>
|
||||
</g>
|
||||
<!-- Umschlag->Kunde_1 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>Umschlag->Kunde_1</title>
|
||||
<path fill="none" stroke="#0891b2" stroke-width="2.5" d="M388.56,-73.22C423.4,-84.11 470.35,-98.8 508.09,-110.6"/>
|
||||
<polygon fill="#0891b2" stroke="#0891b2" stroke-width="2.5" points="506.83,-113.87 517.41,-113.52 508.91,-107.19 506.83,-113.87"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="454.83" y="-103.48" font-family="DejaVu Sans" font-size="9.00" fill="#334155">10 / 20, 1 €</text>
|
||||
</g>
|
||||
<!-- Umschlag->Kunde_2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>Umschlag->Kunde_2</title>
|
||||
<path fill="none" stroke="#0891b2" stroke-width="2.5" d="M388.56,-52.22C423.24,-48.36 469.94,-43.16 507.6,-38.96"/>
|
||||
<polygon fill="#0891b2" stroke="#0891b2" stroke-width="2.5" points="507.71,-42.47 517.26,-37.89 506.94,-35.52 507.71,-42.47"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="454.83" y="-50.53" font-family="DejaVu Sans" font-size="9.00" fill="#334155">5 / 10, 3 €</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8 KiB |
BIN
bilder_04/kap_graphen_vrp_touren.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
1889
bilder_04/kap_graphen_vrp_touren.svg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
bilder_04/kap_lp_simplex_ecken.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
2018
bilder_04/kap_lp_simplex_ecken.svg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
bilder_04/kap_markowitz_restriktionen.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
1873
bilder_04/kap_markowitz_restriktionen.svg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
bilder_04/kap_mehrziel_pareto.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
1788
bilder_04/kap_mehrziel_pareto.svg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
bilder_04/kap_metaheuristiken_landschaft.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
2900
bilder_04/kap_metaheuristiken_landschaft.svg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
bilder_04/kap_milp_runden.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
3177
bilder_04/kap_milp_runden.svg
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
bilder_04/kap_milp_suchbaum.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
89
bilder_04/kap_milp_suchbaum.svg
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="581pt" height="301pt"
|
||||
viewBox="0.00 0.00 581.00 301.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 296.63)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="286.68" y="-277.23" font-family="DejaVu Sans" font-size="12.00">Branch-and-Bound: 5 gelöste LPs statt aller ganzzahligen Punkte</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286.68" y="-262.98" font-family="DejaVu Sans" font-size="12.00">max 5x₁ + 4x₂  u.d.N.  6x₁+4x₂ ≤ 24,  x₁+2x₂ ≤ 6</text>
|
||||
<!-- P₀ -->
|
||||
<g id="node1" class="node">
|
||||
<title>P₀</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M356.91,-256.13C356.91,-256.13 216.88,-256.13 216.88,-256.13 210.88,-256.13 204.88,-250.13 204.88,-244.13 204.88,-244.13 204.88,-216.92 204.88,-216.92 204.88,-210.92 210.88,-204.92 216.88,-204.92 216.88,-204.92 356.91,-204.92 356.91,-204.92 362.91,-204.92 368.91,-210.92 368.91,-216.92 368.91,-216.92 368.91,-244.13 368.91,-244.13 368.91,-250.13 362.91,-256.13 356.91,-256.13"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286.89" y="-240.15" font-family="DejaVu Sans" font-size="10.00">P₀</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286.89" y="-227.4" font-family="DejaVu Sans" font-size="10.00">x = (3; 1,5), Schranke Z ≤ 21</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286.89" y="-214.65" font-family="DejaVu Sans" font-size="10.00">x2 gebrochen — verzweigen</text>
|
||||
</g>
|
||||
<!-- A -->
|
||||
<g id="node2" class="node">
|
||||
<title>A</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M270.41,-153.67C270.41,-153.67 109.38,-153.67 109.38,-153.67 103.38,-153.67 97.37,-147.67 97.37,-141.67 97.37,-141.67 97.37,-114.46 97.37,-114.46 97.37,-108.46 103.37,-102.46 109.37,-102.46 109.37,-102.46 270.41,-102.46 270.41,-102.46 276.41,-102.46 282.41,-108.46 282.41,-114.46 282.41,-114.46 282.41,-141.67 282.41,-141.67 282.41,-147.67 276.41,-153.67 270.41,-153.67"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="189.89" y="-137.69" font-family="DejaVu Sans" font-size="10.00">A</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="189.89" y="-124.94" font-family="DejaVu Sans" font-size="10.00">x = (3,33; 1), Schranke Z ≤ 20,67</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="189.89" y="-112.19" font-family="DejaVu Sans" font-size="10.00">x1 gebrochen — verzweigen</text>
|
||||
</g>
|
||||
<!-- P₀->A -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>P₀->A</title>
|
||||
<path fill="none" stroke="#5c6380" d="M262.42,-204.17C250.24,-191.56 235.36,-176.15 222.26,-162.58"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="224.88,-160.26 215.42,-155.5 219.84,-165.12 224.88,-160.26"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="256.68" y="-176.37" font-family="DejaVu Sans" font-size="9.00">x2 ≤ 1</text>
|
||||
</g>
|
||||
<!-- B -->
|
||||
<g id="node5" class="node">
|
||||
<title>B</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M439.41,-153.67C439.41,-153.67 326.38,-153.67 326.38,-153.67 320.38,-153.67 314.38,-147.67 314.38,-141.67 314.38,-141.67 314.38,-114.46 314.38,-114.46 314.38,-108.46 320.38,-102.46 326.38,-102.46 326.38,-102.46 439.41,-102.46 439.41,-102.46 445.41,-102.46 451.41,-108.46 451.41,-114.46 451.41,-114.46 451.41,-141.67 451.41,-141.67 451.41,-147.67 445.41,-153.67 439.41,-153.67"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="382.89" y="-137.69" font-family="DejaVu Sans" font-size="10.00">B</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="382.89" y="-124.94" font-family="DejaVu Sans" font-size="10.00">Z = 18 ≤ Incumbent 20</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="382.89" y="-112.19" font-family="DejaVu Sans" font-size="10.00">→ gekappt</text>
|
||||
</g>
|
||||
<!-- P₀->B -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>P₀->B</title>
|
||||
<path fill="none" stroke="#5c6380" d="M311.12,-204.17C323.17,-191.56 337.9,-176.15 350.87,-162.58"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="353.25,-165.15 357.63,-155.5 348.19,-160.31 353.25,-165.15"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="353.15" y="-176.37" font-family="DejaVu Sans" font-size="9.00">x2 ≥ 2</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A1</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M161.79,-51.21C161.79,-51.21 12,-51.21 12,-51.21 6,-51.21 0,-45.21 0,-39.21 0,-39.21 0,-12 0,-12 0,-6 6,0 12,0 12,0 161.79,0 161.79,0 167.79,0 173.79,-6 173.79,-12 173.79,-12 173.79,-39.21 173.79,-39.21 173.79,-45.21 167.79,-51.21 161.79,-51.21"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.89" y="-35.23" font-family="DejaVu Sans" font-size="10.00">A1</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">x = (3; 1), Z = 19</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">ganzzahlig — neuer Incumbent</text>
|
||||
</g>
|
||||
<!-- A->A1 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A->A1</title>
|
||||
<path fill="none" stroke="#5c6380" d="M163.9,-101.71C150.85,-88.98 134.87,-73.4 120.86,-59.74"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="123.56,-57.48 113.96,-53 118.68,-62.49 123.56,-57.48"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="156.86" y="-73.91" font-family="DejaVu Sans" font-size="9.00">x1 ≤ 3</text>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A2</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M367.79,-51.21C367.79,-51.21 218,-51.21 218,-51.21 212,-51.21 206,-45.21 206,-39.21 206,-39.21 206,-12 206,-12 206,-6 212,0 218,0 218,0 367.79,0 367.79,0 373.79,0 379.79,-6 379.79,-12 379.79,-12 379.79,-39.21 379.79,-39.21 379.79,-45.21 373.79,-51.21 367.79,-51.21"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="292.89" y="-35.23" font-family="DejaVu Sans" font-size="10.00">A2</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="292.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">x = (4; 0), Z = 20</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="292.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">ganzzahlig — neuer Incumbent</text>
|
||||
</g>
|
||||
<!-- A->A2 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>A->A2</title>
|
||||
<path fill="none" stroke="#5c6380" d="M215.89,-101.71C228.94,-88.98 244.92,-73.4 258.93,-59.74"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="261.11,-62.49 265.83,-53 256.23,-57.48 261.11,-62.49"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="259.86" y="-73.91" font-family="DejaVu Sans" font-size="9.00">x1 ≥ 4</text>
|
||||
</g>
|
||||
<!-- ergebnis -->
|
||||
<g id="node6" class="node">
|
||||
<title>ergebnis</title>
|
||||
<polygon fill="#f8fafc" stroke="#94a3b8" points="567.36,-248.53 400.44,-248.53 400.44,-212.53 573.36,-212.53 573.36,-242.53 567.36,-248.53"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="567.36,-248.53 567.36,-242.53"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="573.36,-242.53 567.36,-242.53"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="486.89" y="-227.4" font-family="DejaVu Sans" font-size="10.00">Optimum: x* = (4; 0), Z* = 20</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
BIN
bilder_04/kap_praxisfallen_schichten.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
103
bilder_04/kap_praxisfallen_schichten.svg
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="709pt" height="369pt"
|
||||
viewBox="0.00 0.00 709.00 369.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 364.66)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="350.58" y="-344.31" font-family="DejaVu Sans" font-size="13.00">Die Schichten von or_kern.py — alle Pfeile zeigen nach innen</text>
|
||||
<!-- io -->
|
||||
<g id="node1" class="node">
|
||||
<title>io</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M248.04,-237.7C248.04,-237.7 12,-237.7 12,-237.7 6,-237.7 0,-231.7 0,-225.7 0,-225.7 0,-198.49 0,-198.49 0,-192.49 6,-186.49 12,-186.49 12,-186.49 248.04,-186.49 248.04,-186.49 254.04,-186.49 260.04,-192.49 260.04,-198.49 260.04,-198.49 260.04,-225.7 260.04,-225.7 260.04,-231.7 254.04,-237.7 248.04,-237.7"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="130.02" y="-221.72" font-family="DejaVu Sans" font-size="10.00">Ein- und Ausgabe</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="130.02" y="-208.97" font-family="DejaVu Sans" font-size="10.00">lade_produktionsproblem() · schreibe_ergebnis()</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="130.02" y="-196.22" font-family="DejaVu Sans" font-size="10.00">Excel, CSV — die Wirklichkeit des Anwenders</text>
|
||||
</g>
|
||||
<!-- dom -->
|
||||
<g id="node5" class="node">
|
||||
<title>dom</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M465.04,-139.24C465.04,-139.24 271,-139.24 271,-139.24 265,-139.24 259,-133.24 259,-127.24 259,-127.24 259,-100.03 259,-100.03 259,-94.03 265,-88.03 271,-88.03 271,-88.03 465.04,-88.03 465.04,-88.03 471.04,-88.03 477.04,-94.03 477.04,-100.03 477.04,-100.03 477.04,-127.24 477.04,-127.24 477.04,-133.24 471.04,-139.24 465.04,-139.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="368.02" y="-123.26" font-family="DejaVu Sans" font-size="10.00">Domäne</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="368.02" y="-110.51" font-family="DejaVu Sans" font-size="10.00">Produkt · Produktionsproblem</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="368.02" y="-97.76" font-family="DejaVu Sans" font-size="10.00">validiert (Pydantic), kennt keinen Solver</text>
|
||||
</g>
|
||||
<!-- io->dom -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>io->dom</title>
|
||||
<path fill="none" stroke="#5c6380" d="M153.56,-185.74C166.52,-171.78 180.02,-157.24 180.02,-157.24 180.02,-157.24 210.63,-150.3 247.15,-142.03"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="247.66,-145.5 256.64,-139.88 246.11,-138.67 247.66,-145.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="235.52" y="-159.94" font-family="DejaVu Sans" font-size="9.00">liest ein, schreibt zurück</text>
|
||||
</g>
|
||||
<!-- bau -->
|
||||
<g id="node2" class="node">
|
||||
<title>bau</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M432.04,-336.16C432.04,-336.16 256,-336.16 256,-336.16 250,-336.16 244,-330.16 244,-324.16 244,-324.16 244,-296.95 244,-296.95 244,-290.95 250,-284.95 256,-284.95 256,-284.95 432.04,-284.95 432.04,-284.95 438.04,-284.95 444.04,-290.95 444.04,-296.95 444.04,-296.95 444.04,-324.16 444.04,-324.16 444.04,-330.16 438.04,-336.16 432.04,-336.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="344.02" y="-320.18" font-family="DejaVu Sans" font-size="10.00">Modellbauer — austauschbar</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="344.02" y="-307.43" font-family="DejaVu Sans" font-size="10.00">loese_mit_glop() · loese_mit_scipy()</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="344.02" y="-294.68" font-family="DejaVu Sans" font-size="10.00">je Solver einer, gleiche Signatur</text>
|
||||
</g>
|
||||
<!-- erg -->
|
||||
<g id="node4" class="node">
|
||||
<title>erg</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M588.66,-237.7C588.66,-237.7 437.38,-237.7 437.38,-237.7 431.38,-237.7 425.38,-231.7 425.38,-225.7 425.38,-225.7 425.38,-198.49 425.38,-198.49 425.38,-192.49 431.38,-186.49 437.38,-186.49 437.38,-186.49 588.66,-186.49 588.66,-186.49 594.66,-186.49 600.66,-192.49 600.66,-198.49 600.66,-198.49 600.66,-225.7 600.66,-225.7 600.66,-231.7 594.66,-237.7 588.66,-237.7"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="513.02" y="-221.72" font-family="DejaVu Sans" font-size="10.00">Ergebnis — solverunabhängig</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="513.02" y="-208.97" font-family="DejaVu Sans" font-size="10.00">SolverStatus · Loesung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="513.02" y="-196.22" font-family="DejaVu Sans" font-size="10.00">fünf Bibliotheken, eine Sprache</text>
|
||||
</g>
|
||||
<!-- bau->erg -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>bau->erg</title>
|
||||
<path fill="none" stroke="#5c6380" d="M388.87,-283.96C410.25,-271.76 436.01,-257.05 458.51,-244.21"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="459.99,-247.39 466.94,-239.39 456.52,-241.31 459.99,-247.39"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="446.34" y="-258.4" font-family="DejaVu Sans" font-size="9.00">liefert</text>
|
||||
</g>
|
||||
<!-- bau->dom -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>bau->dom</title>
|
||||
<path fill="none" stroke="#5c6380" d="M336.56,-284.02C330.52,-263.37 323.02,-237.7 323.02,-237.7 323.02,-237.7 323.02,-186.49 323.02,-186.49 323.02,-186.49 334.57,-168.05 345.97,-149.85"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="348.88,-151.79 351.22,-141.45 342.95,-148.07 348.88,-151.79"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="368.02" y="-209.17" font-family="DejaVu Sans" font-size="9.00">baut das Modell aus</text>
|
||||
</g>
|
||||
<!-- pruef -->
|
||||
<g id="node3" class="node">
|
||||
<title>pruef</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M689.16,-336.16C689.16,-336.16 546.88,-336.16 546.88,-336.16 540.88,-336.16 534.88,-330.16 534.88,-324.16 534.88,-324.16 534.88,-296.95 534.88,-296.95 534.88,-290.95 540.88,-284.95 546.88,-284.95 546.88,-284.95 689.16,-284.95 689.16,-284.95 695.16,-284.95 701.16,-290.95 701.16,-296.95 701.16,-296.95 701.16,-324.16 701.16,-324.16 701.16,-330.16 695.16,-336.16 689.16,-336.16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="618.02" y="-320.18" font-family="DejaVu Sans" font-size="10.00">Abnahmeprüfung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="618.02" y="-307.43" font-family="DejaVu Sans" font-size="10.00">pruefe_loesung()</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="618.02" y="-294.68" font-family="DejaVu Sans" font-size="10.00">prüft die Lösung OHNE Solver</text>
|
||||
</g>
|
||||
<!-- pruef->erg -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>pruef->erg</title>
|
||||
<path fill="none" stroke="#5c6380" d="M590.16,-283.96C577.48,-272.31 562.32,-258.39 548.81,-245.97"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="551.49,-243.69 541.76,-239.5 546.76,-248.84 551.49,-243.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="579.92" y="-258.4" font-family="DejaVu Sans" font-size="9.00">prüft</text>
|
||||
</g>
|
||||
<!-- pruef->dom -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>pruef->dom</title>
|
||||
<path fill="none" stroke="#5c6380" d="M616.98,-284.06C615.52,-248.01 613.02,-186.49 613.02,-186.49 613.02,-186.49 534.17,-163.36 465.93,-143.35"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="467.12,-140.05 456.54,-140.6 465.15,-146.77 467.12,-140.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="642.02" y="-209.17" font-family="DejaVu Sans" font-size="9.00">prüft gegen</text>
|
||||
</g>
|
||||
<!-- erg->dom -->
|
||||
<!-- regel -->
|
||||
<g id="node6" class="node">
|
||||
<title>regel</title>
|
||||
<polygon fill="#f8fafc" stroke="#94a3b8" points="487.85,-51.03 242.19,-51.03 242.19,0 493.85,0 493.85,-45.03 487.85,-51.03"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="487.85,-51.03 487.85,-45.03"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="493.85,-45.03 487.85,-45.03"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="255.14" y="-33.84" font-family="DejaVu Sans" font-size="9.00">Die Domäne importiert nichts von außen.</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="255.14" y="-22.59" font-family="DejaVu Sans" font-size="9.00">Deshalb kostet ein Solverwechsel eine Funktion —</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="255.14" y="-11.34" font-family="DejaVu Sans" font-size="9.00">und kein einziges Fachdetail.</text>
|
||||
</g>
|
||||
<!-- dom->regel -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dom->regel</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M368.02,-87.26C368.02,-75.95 368.02,-62.67 368.02,-51.41"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
BIN
bilder_04/kap_qp_kkt.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
3473
bilder_04/kap_qp_kkt.svg
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
bilder_04/kap_testing_dienst.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
144
bilder_04/kap_testing_dienst.svg
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="496pt" height="553pt"
|
||||
viewBox="0.00 0.00 496.00 553.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 549.04)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="244.04" y="-528.69" font-family="DejaVu Sans" font-size="13.00">Optimierungsdienst: warum die Antwort 202 lautet, nicht 200</text>
|
||||
<!-- klient -->
|
||||
<g id="node1" class="node">
|
||||
<title>klient</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M237.91,-520.54C237.91,-520.54 129.38,-520.54 129.38,-520.54 123.38,-520.54 117.37,-514.54 117.37,-508.54 117.37,-508.54 117.37,-494.08 117.37,-494.08 117.37,-488.08 123.37,-482.08 129.38,-482.08 129.38,-482.08 237.91,-482.08 237.91,-482.08 243.91,-482.08 249.91,-488.08 249.91,-494.08 249.91,-494.08 249.91,-508.54 249.91,-508.54 249.91,-514.54 243.91,-520.54 237.91,-520.54"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="183.64" y="-504.56" font-family="DejaVu Sans" font-size="10.00">Aufrufer</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="183.64" y="-491.81" font-family="DejaVu Sans" font-size="10.00">(curl, Fachanwendung)</text>
|
||||
</g>
|
||||
<!-- post -->
|
||||
<g id="node2" class="node">
|
||||
<title>post</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M394.04,-445.08C394.04,-445.08 283.25,-445.08 283.25,-445.08 277.25,-445.08 271.25,-439.08 271.25,-433.08 271.25,-433.08 271.25,-418.62 271.25,-418.62 271.25,-412.62 277.25,-406.62 283.25,-406.62 283.25,-406.62 394.04,-406.62 394.04,-406.62 400.04,-406.62 406.04,-412.62 406.04,-418.62 406.04,-418.62 406.04,-433.08 406.04,-433.08 406.04,-439.08 400.04,-445.08 394.04,-445.08"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.64" y="-429.1" font-family="DejaVu Sans" font-size="10.00">POST /plaene</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.64" y="-416.35" font-family="DejaVu Sans" font-size="10.00">JSON mit dem Problem</text>
|
||||
</g>
|
||||
<!-- klient->post -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>klient->post</title>
|
||||
<path fill="none" stroke="#5c6380" d="M223.96,-481.2C243.53,-471.93 267.24,-460.69 288.02,-450.85"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="289.35,-454.08 296.89,-446.64 286.35,-447.76 289.35,-454.08"/>
|
||||
</g>
|
||||
<!-- get -->
|
||||
<g id="node7" class="node">
|
||||
<title>get</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M157.29,-44.75C157.29,-44.75 12,-44.75 12,-44.75 6,-44.75 0,-38.75 0,-32.75 0,-32.75 0,-18.28 0,-18.28 0,-12.28 6,-6.29 12,-6.29 12,-6.29 157.29,-6.29 157.29,-6.29 163.29,-6.29 169.29,-12.29 169.29,-18.29 169.29,-18.29 169.29,-32.75 169.29,-32.75 169.29,-38.75 163.29,-44.75 157.29,-44.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84.64" y="-28.77" font-family="DejaVu Sans" font-size="10.00">GET /plaene/{id}</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="84.64" y="-16.02" font-family="DejaVu Sans" font-size="10.00">laeuft · fertig · fehlgeschlagen</text>
|
||||
</g>
|
||||
<!-- klient->get -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>klient->get</title>
|
||||
<path fill="none" stroke="#5c6380" d="M157.63,-481.2C128,-459.44 83.64,-426.85 83.64,-426.85 83.64,-426.85 83.64,-426.85 83.64,-116.51 83.64,-116.51 84.02,-83.05 84.31,-56.94"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="87.8,-57.15 84.42,-47.11 80.81,-57.07 87.8,-57.15"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="107.27" y="-255.9" font-family="DejaVu Sans" font-size="9.00">fragt nach</text>
|
||||
</g>
|
||||
<!-- gesund -->
|
||||
<g id="node8" class="node">
|
||||
<title>gesund</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M233.79,-445.08C233.79,-445.08 133.5,-445.08 133.5,-445.08 127.5,-445.08 121.5,-439.08 121.5,-433.08 121.5,-433.08 121.5,-418.62 121.5,-418.62 121.5,-412.62 127.5,-406.62 133.5,-406.62 133.5,-406.62 233.79,-406.62 233.79,-406.62 239.79,-406.62 245.79,-412.62 245.79,-418.62 245.79,-418.62 245.79,-433.08 245.79,-433.08 245.79,-439.08 239.79,-445.08 233.79,-445.08"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="183.64" y="-429.1" font-family="DejaVu Sans" font-size="10.00">GET /gesundheit</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="183.64" y="-416.35" font-family="DejaVu Sans" font-size="10.00">für die Überwachung</text>
|
||||
</g>
|
||||
<!-- klient->gesund -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>klient->gesund</title>
|
||||
<path fill="none" stroke="#5c6380" stroke-dasharray="1,5" d="M183.64,-481.39C183.64,-474.08 183.64,-465.53 183.64,-457.39"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="187.15,-457.47 183.65,-447.47 180.15,-457.47 187.15,-457.47"/>
|
||||
</g>
|
||||
<!-- valid -->
|
||||
<g id="node3" class="node">
|
||||
<title>valid</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M317.6,-354.68C317.6,-354.68 249.15,-325.6 249.15,-325.6 243.63,-323.26 243.63,-318.56 249.15,-316.22 249.15,-316.22 317.6,-287.14 317.6,-287.14 323.12,-284.8 334.17,-284.8 339.69,-287.14 339.69,-287.14 408.14,-316.22 408.14,-316.22 413.66,-318.56 413.66,-323.26 408.14,-325.6 408.14,-325.6 339.69,-354.68 339.69,-354.68 334.17,-357.02 323.12,-357.02 317.6,-354.68"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.64" y="-324.16" font-family="DejaVu Sans" font-size="10.00">Pydantic prüft</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.64" y="-311.41" font-family="DejaVu Sans" font-size="10.00">die Eingabe</text>
|
||||
</g>
|
||||
<!-- post->valid -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>post->valid</title>
|
||||
<path fill="none" stroke="#5c6380" d="M336.81,-405.97C335.82,-395.73 334.54,-382.58 333.29,-369.73"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="336.81,-369.71 332.35,-360.1 329.84,-370.39 336.81,-369.71"/>
|
||||
</g>
|
||||
<!-- fehler -->
|
||||
<g id="node4" class="node">
|
||||
<title>fehler</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M247.29,-235.2C247.29,-235.2 144,-235.2 144,-235.2 138,-235.2 132,-229.2 132,-223.2 132,-223.2 132,-195.99 132,-195.99 132,-189.99 138,-183.99 144,-183.99 144,-183.99 247.29,-183.99 247.29,-183.99 253.29,-183.99 259.29,-189.99 259.29,-195.99 259.29,-195.99 259.29,-223.2 259.29,-223.2 259.29,-229.2 253.29,-235.2 247.29,-235.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="195.64" y="-219.22" font-family="DejaVu Sans" font-size="10.00">422</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="195.64" y="-206.47" font-family="DejaVu Sans" font-size="10.00">Fehler benannt,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="195.64" y="-193.72" font-family="DejaVu Sans" font-size="10.00">bevor ein Solver läuft</text>
|
||||
</g>
|
||||
<!-- valid->fehler -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>valid->fehler</title>
|
||||
<path fill="none" stroke="#5c6380" d="M297.8,-294.56C279.3,-279.35 255.57,-259.85 235.64,-243.47"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="237.9,-240.8 227.95,-237.15 233.46,-246.2 237.9,-240.8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="277.28" y="-255.9" font-family="DejaVu Sans" font-size="9.00">ungültig</text>
|
||||
</g>
|
||||
<!-- an -->
|
||||
<g id="node5" class="node">
|
||||
<title>an</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M361.16,-228.83C361.16,-228.83 296.12,-228.83 296.12,-228.83 290.12,-228.83 284.12,-222.83 284.12,-216.83 284.12,-216.83 284.12,-202.37 284.12,-202.37 284.12,-196.37 290.12,-190.37 296.12,-190.37 296.12,-190.37 361.16,-190.37 361.16,-190.37 367.16,-190.37 373.16,-196.37 373.16,-202.37 373.16,-202.37 373.16,-216.83 373.16,-216.83 373.16,-222.83 367.16,-228.83 361.16,-228.83"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.64" y="-212.85" font-family="DejaVu Sans" font-size="10.00">202 Accepted</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.64" y="-200.1" font-family="DejaVu Sans" font-size="10.00">+ Auftrags-ID</text>
|
||||
</g>
|
||||
<!-- valid->an -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>valid->an</title>
|
||||
<path fill="none" stroke="#5c6380" d="M328.64,-281.37C328.64,-268.14 328.64,-253.53 328.64,-241.03"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="332.15,-241.33 328.65,-231.33 325.15,-241.33 332.15,-241.33"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="341.77" y="-255.9" font-family="DejaVu Sans" font-size="9.00">gültig</text>
|
||||
</g>
|
||||
<!-- an->klient -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>an->klient</title>
|
||||
<path fill="none" stroke="#5c6380" stroke-dasharray="5,2" d="M355.98,-229.66C386.49,-250.95 431.64,-282.45 431.64,-282.45 431.64,-282.45 431.64,-359.37 431.64,-359.37 431.64,-359.37 418.64,-445.08 418.64,-445.08 418.64,-445.08 329.88,-465.94 261.46,-482.02"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="261.08,-478.52 252.15,-484.21 262.68,-485.33 261.08,-478.52"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="458.46" y="-380.07" font-family="DejaVu Sans" font-size="9.00">sofort zurück</text>
|
||||
</g>
|
||||
<!-- rechne -->
|
||||
<g id="node6" class="node">
|
||||
<title>rechne</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M368.54,-136.74C368.54,-136.74 266.75,-136.74 266.75,-136.74 260.75,-136.74 254.75,-130.74 254.75,-124.74 254.75,-124.74 254.75,-110.28 254.75,-110.28 254.75,-104.28 260.75,-98.28 266.75,-98.28 266.75,-98.28 368.54,-98.28 368.54,-98.28 374.54,-98.28 380.54,-104.28 380.54,-110.28 380.54,-110.28 380.54,-124.74 380.54,-124.74 380.54,-130.74 374.54,-136.74 368.54,-136.74"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="317.64" y="-120.76" font-family="DejaVu Sans" font-size="10.00">Hintergrundrechnung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="317.64" y="-108.01" font-family="DejaVu Sans" font-size="10.00">rechne() → or_kern</text>
|
||||
</g>
|
||||
<!-- an->rechne -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>an->rechne</title>
|
||||
<path fill="none" stroke="#5c6380" d="M326.31,-189.49C324.87,-177.67 322.98,-162.22 321.34,-148.76"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="324.84,-148.59 320.16,-139.09 317.9,-149.44 324.84,-148.59"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="338.14" y="-157.44" font-family="DejaVu Sans" font-size="9.00">startet</text>
|
||||
</g>
|
||||
<!-- rechne->get -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>rechne->get</title>
|
||||
<path fill="none" stroke="#5c6380" stroke-dasharray="5,2" d="M258.5,-97.33C231.69,-88.64 205.89,-80.28 205.89,-80.28 205.89,-80.28 170.63,-64.64 138.14,-50.24"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="139.68,-47.09 129.12,-46.24 136.84,-53.49 139.68,-47.09"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="258.77" y="-71.73" font-family="DejaVu Sans" font-size="9.00">hinterlegt das Ergebnis</text>
|
||||
</g>
|
||||
<!-- regel -->
|
||||
<g id="node9" class="node">
|
||||
<title>regel</title>
|
||||
<polygon fill="#f8fafc" stroke="#94a3b8" points="434.48,-51.03 194.81,-51.03 194.81,0 440.48,0 440.48,-45.03 434.48,-51.03"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="434.48,-51.03 434.48,-45.03"/>
|
||||
<polyline fill="none" stroke="#94a3b8" points="440.48,-45.03 434.48,-45.03"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="207.77" y="-33.84" font-family="DejaVu Sans" font-size="9.00">Eine Optimierung dauert Sekunden bis Minuten.</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="207.77" y="-22.59" font-family="DejaVu Sans" font-size="9.00">Synchron beantwortet, blockiert sie den Dienst</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="207.77" y="-11.34" font-family="DejaVu Sans" font-size="9.00">und läuft in jedes Gateway-Timeout.</text>
|
||||
</g>
|
||||
<!-- rechne->regel -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>rechne->regel</title>
|
||||
<path fill="none" stroke="#94a3b8" stroke-dasharray="5,2" d="M317.64,-97.42C317.64,-84.01 317.64,-65.93 317.64,-51.39"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
123
bilder_04/or_html_style.html
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<style>
|
||||
:root{
|
||||
--ink:#1A1A2E; --indigo:#4338CA; --teal:#0891B2; --violet:#7C3AED;
|
||||
--emerald:#059669; --amber:#D97706; --rose:#E11D48; --slate:#334155;
|
||||
--tint-indigo:#EEF2FF; --tint-violet:#F5F3FF; --line:#E2E8F0;
|
||||
--code-bg:#1e293b;
|
||||
}
|
||||
*{box-sizing:border-box;}
|
||||
html{scroll-behavior:smooth;}
|
||||
body{
|
||||
font-family:"Segoe UI",-apple-system,BlinkMacSystemFont,"DejaVu Sans",Arial,sans-serif;
|
||||
color:var(--ink); background:#ffffff; line-height:1.7;
|
||||
max-width:900px; margin:0 auto; padding:1.2rem 1.4rem 6rem;
|
||||
font-size:17px; -webkit-text-size-adjust:100%;
|
||||
}
|
||||
h1,h2,h3,h4,h5{line-height:1.25; font-weight:700; margin-top:2.2em; margin-bottom:.6em;
|
||||
scroll-margin-top:1rem;}
|
||||
h1{color:var(--indigo); font-size:2.15rem; border-bottom:4px solid var(--indigo); padding-bottom:.25em;}
|
||||
h2{color:var(--indigo); font-size:1.6rem; border-bottom:2px solid var(--line); padding-bottom:.2em;}
|
||||
h3{color:var(--teal); font-size:1.28rem;}
|
||||
h4{color:var(--violet); font-size:1.1rem;}
|
||||
h5{color:var(--slate); font-size:1rem; text-transform:uppercase; letter-spacing:.03em;}
|
||||
a{color:var(--indigo); text-decoration:none;}
|
||||
a:hover{text-decoration:underline;}
|
||||
/* Externe Links: Öffnen im neuen Tab + Markierung */
|
||||
a[target="_blank"]::after{content:" \2197"; font-size:.82em; color:var(--teal); text-decoration:none;}
|
||||
p{margin:.75em 0;}
|
||||
hr{border:0; border-top:1px solid var(--line); margin:2.2em 0;}
|
||||
|
||||
/* Inhaltsverzeichnis */
|
||||
nav#TOC{background:var(--tint-indigo); border:1px solid #C7D2FE; border-radius:12px;
|
||||
padding:1.1em 1.6em 1.3em; margin:2em 0;}
|
||||
nav#TOC::before{content:"Inhalt"; display:block; font-weight:700; color:var(--indigo);
|
||||
font-size:1.35rem; margin-bottom:.4em;}
|
||||
nav#TOC ul{list-style:none; padding-left:1.1em; margin:.15em 0;}
|
||||
nav#TOC > ul{padding-left:0;}
|
||||
nav#TOC li{margin:.22em 0;}
|
||||
nav#TOC a{color:var(--slate); text-decoration:none;}
|
||||
nav#TOC a:hover{color:var(--indigo); text-decoration:underline;}
|
||||
nav#TOC > ul > li > a{font-weight:700; color:var(--indigo);}
|
||||
nav#TOC a[target="_blank"]::after{content:"";} /* keine Pfeile im TOC */
|
||||
|
||||
/* Bilder / Figuren */
|
||||
figure{margin:2em 0; text-align:center;}
|
||||
img{max-width:100%; height:auto; display:block; margin:0 auto; border-radius:10px;}
|
||||
figure img{box-shadow:0 6px 24px rgba(26,26,46,.10);}
|
||||
figcaption{color:var(--slate); font-size:.92rem; font-style:italic; margin-top:.7em;}
|
||||
body > figure:first-of-type img{box-shadow:0 10px 40px rgba(67,56,202,.15);}
|
||||
body > figure:first-of-type figcaption{display:none;}
|
||||
|
||||
/* Tabellen */
|
||||
table{border-collapse:collapse; width:100%; margin:1.4em 0; font-size:.96rem;
|
||||
box-shadow:0 2px 10px rgba(26,26,46,.06); border-radius:8px; overflow:hidden;}
|
||||
th{background:var(--indigo); color:#fff; font-weight:700; text-align:left;}
|
||||
th,td{padding:.6em .8em; border:1px solid var(--line); vertical-align:top;}
|
||||
tbody tr:nth-child(even){background:var(--tint-indigo);}
|
||||
|
||||
/* Code – dunkler Kasten, heller Text (breezedark) */
|
||||
code{font-family:"DejaVu Sans Mono",Consolas,Menlo,monospace; font-size:.9em;
|
||||
background:var(--tint-violet); color:#5b21b6; padding:.12em .4em; border-radius:5px;}
|
||||
pre{background:var(--code-bg); color:#e6edf3; padding:1.1em 1.2em; border-radius:10px;
|
||||
overflow-x:auto; line-height:1.5; font-size:.86rem;
|
||||
box-shadow:0 4px 18px rgba(15,23,42,.25);}
|
||||
pre code{background:none; color:inherit; padding:0; font-size:inherit;}
|
||||
pre .co{color:#8b9bb4 !important; font-style:italic;} /* Kommentare gut lesbar */
|
||||
|
||||
/* Copy-Button für Codeblöcke */
|
||||
div.sourceCode{position:relative;}
|
||||
div.sourceCode pre.sourceCode{padding-top:2.6em;}
|
||||
.copy-btn{
|
||||
position:absolute; top:.6em; right:.6em; z-index:2;
|
||||
display:flex; align-items:center; gap:.35em;
|
||||
background:rgba(255,255,255,.08); color:#cbd5e1;
|
||||
border:1px solid rgba(255,255,255,.18); border-radius:6px;
|
||||
padding:.3em .65em; font:inherit; font-size:.78rem;
|
||||
cursor:pointer; transition:background .15s,color .15s,border-color .15s;
|
||||
}
|
||||
.copy-btn:hover{background:rgba(255,255,255,.18); color:#fff;}
|
||||
.copy-btn svg{width:1em; height:1em; flex:none;}
|
||||
.copy-btn.copied{background:var(--emerald); color:#fff; border-color:var(--emerald);}
|
||||
|
||||
/* Blockquote */
|
||||
blockquote{border-left:4px solid var(--violet); background:var(--tint-violet);
|
||||
margin:1.4em 0; padding:.6em 1.2em; border-radius:0 8px 8px 0; color:#3b2e63;}
|
||||
|
||||
/* Mathe */
|
||||
.katex-display{overflow-x:auto; overflow-y:hidden; padding:.4em 0;}
|
||||
|
||||
ul,ol{padding-left:1.5em;} li{margin:.3em 0;}
|
||||
|
||||
@media (max-width:640px){ body{font-size:16px; padding:1rem;} h1{font-size:1.8rem;} }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.querySelectorAll("div.sourceCode").forEach(function (box) {
|
||||
var code = box.querySelector("code");
|
||||
if (!code) return;
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "copy-btn";
|
||||
btn.setAttribute("aria-label", "Code kopieren");
|
||||
btn.innerHTML =
|
||||
'<svg 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"/></svg>' +
|
||||
'<span>Kopieren</span>';
|
||||
btn.addEventListener("click", function () {
|
||||
navigator.clipboard.writeText(code.innerText).then(function () {
|
||||
var label = btn.querySelector("span");
|
||||
var original = label.textContent;
|
||||
btn.classList.add("copied");
|
||||
label.textContent = "Kopiert!";
|
||||
setTimeout(function () {
|
||||
btn.classList.remove("copied");
|
||||
label.textContent = original;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
box.appendChild(btn);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
176
bilder_04/or_pdf_header.tex
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
% or_pdf_header.tex
|
||||
% Gemeinsame LaTeX-Zusatzpraeambel fuer den PDF-Bau der Arbeitskopie UND
|
||||
% der Version 02. Einbindung per pandoc --include-in-header <diese Datei>.
|
||||
|
||||
% --- SVG-Grafiken auf die Textbreite begrenzen -----------------------------
|
||||
% Pandoc setzt \setkeys{Gin}{width=\maxwidth,...}, damit zu breite Bilder
|
||||
% automatisch verkleinert werden. Das wirkt aber nur auf \includegraphics.
|
||||
% SVG-Bilder bindet pandoc als \includesvg{...} OHNE Breitenangabe ein; das
|
||||
% Paket svg rendert sie dann in ihrer natuerlichen Groesse - und alles, was
|
||||
% breiter als der Textblock ist, laeuft ueber den rechten Rand hinaus.
|
||||
%
|
||||
% Der folgende Wrapper stellt das pandoc-Verhalten fuer \includesvg her:
|
||||
% Bilder werden auf \linewidth verkleinert, wenn sie breiter sind, und
|
||||
% ansonsten unveraendert gesetzt (also nie kuenstlich vergroessert).
|
||||
%
|
||||
% \LetLtxMacro statt \let ist noetig, weil \includesvg ein optionales
|
||||
% Argument hat; ein einfaches \let wuerde zu einer Endlosrekursion fuehren.
|
||||
%
|
||||
% Die Abfrage auf \includesvg ist wichtig: Pandoc laedt das Paket svg nur
|
||||
% dann, wenn das Dokument ueberhaupt SVG-Bilder enthaelt. Ohne die Abfrage
|
||||
% scheitert der Lauf bei Dokumenten ohne SVG mit "Undefined control sequence".
|
||||
\usepackage{letltxmacro}
|
||||
\makeatletter
|
||||
\@ifundefined{includesvg}{}{%
|
||||
\LetLtxMacro\origincludesvg\includesvg
|
||||
\renewcommand*{\includesvg}[2][]{%
|
||||
\begingroup
|
||||
\sbox0{\origincludesvg[#1]{#2}}%
|
||||
\ifdim\wd0>\linewidth
|
||||
\resizebox{\linewidth}{!}{\usebox0}%
|
||||
\else
|
||||
\usebox0
|
||||
\fi
|
||||
\endgroup}%
|
||||
}
|
||||
\makeatother
|
||||
|
||||
% --- Lange Codezeilen umbrechen statt ueber den Rand laufen lassen ----------
|
||||
% fvextra erweitert die von pandoc verwendete fancyvrb-Umgebung um
|
||||
% Zeilenumbruch. Ohne das werden Zeilen jenseits der Textbreite abgeschnitten.
|
||||
\usepackage{fvextra}
|
||||
\fvset{
|
||||
breaklines=true, % lange Zeilen umbrechen
|
||||
breakanywhere=true, % notfalls auch mitten im Wort (lange Strings)
|
||||
breaksymbolleft={\tiny\ensuremath{\hookrightarrow}}, % Fortsetzungszeichen
|
||||
fontsize=\small,
|
||||
}
|
||||
|
||||
% Auch fuer die von pandoc erzeugte Highlighting-Umgebung wirksam machen
|
||||
% (das sind die Bloecke mit Sprachangabe, z. B. ```python).
|
||||
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{
|
||||
commandchars=\\\{\},
|
||||
breaklines=true,
|
||||
breakanywhere=true,
|
||||
breaksymbolleft={\tiny\ensuremath{\hookrightarrow}},
|
||||
fontsize=\small
|
||||
}
|
||||
|
||||
% Bloecke OHNE Sprachangabe (```) erzeugt pandoc als klassisches verbatim.
|
||||
% Das kennt keinen Zeilenumbruch und laeuft daher ebenfalls ueber den Rand -
|
||||
% betroffen sind hier vor allem die abgedruckten Programmausgaben.
|
||||
% \RecustomVerbatimEnvironment baut verbatim auf fancyvrbs Verbatim um.
|
||||
\RecustomVerbatimEnvironment{verbatim}{Verbatim}{
|
||||
breaklines=true,
|
||||
breakanywhere=true,
|
||||
breaksymbolleft={\tiny\ensuremath{\hookrightarrow}},
|
||||
fontsize=\small
|
||||
}
|
||||
|
||||
% --- Tabellen etwas kompakter, damit breite Tabellen besser passen ---------
|
||||
\usepackage{array}
|
||||
\renewcommand{\arraystretch}{1.08}
|
||||
|
||||
% --- Umbruchverhalten fuer lange URLs und Bezeichner -----------------------
|
||||
% Lange Bezeichner wie Kap_05_MILP_Portfolio_Fixgebuehren.py setzt pandoc als
|
||||
% \texttt{...}. LaTeX trennt darin nicht und laesst solche Namen ueber den
|
||||
% rechten Rand laufen, wenn sie ans Zeilenende geraten. Ein \allowbreak nach
|
||||
% jedem Unterstrich erlaubt einen Umbruch an genau diesen Stellen.
|
||||
% Bewusst OHNE Trennstrich: Ein Bindestrich waere hier irrefuehrend, weil er
|
||||
% wie ein Bestandteil des Dateinamens aussaehe.
|
||||
\let\origtextunderscore\_
|
||||
\renewcommand{\_}{\origtextunderscore\allowbreak}
|
||||
|
||||
\sloppy % lieber grosszuegigere Wortabstaende als Ueberhang
|
||||
\emergencystretch=3em
|
||||
|
||||
% --- Stichwortregister (Phase 4) --------------------------------------------
|
||||
% Begriffe werden im Markdown mit {idx:Begriff} bzw. fuer Unterbegriffe mit
|
||||
% {idx:Oberbegriff!Unterbegriff} markiert (siehe resolve_index() in
|
||||
% build_version_03.py) und dort zu einem rohen `\index{...}`{=latex}-Block
|
||||
% aufgeloest. Fuer HTML wird dieser Rohblock von Pandoc automatisch verworfen
|
||||
% (kein Seitenzahlen-Konzept dort) - kein Sonderfall im Buildskript notwendig.
|
||||
% Sortierung uebernimmt texindy (xindy mit German-Modul) statt makeindex,
|
||||
% damit Umlaute korrekt einsortiert werden (ä nach a, ö nach o, ü nach u),
|
||||
% ohne dass Autor:innen von Hand ASCII-Sortierschluessel angeben muessen.
|
||||
\usepackage{makeidx}
|
||||
\makeindex
|
||||
|
||||
% --- Modernes Layout: Ueberschriften, Kopfzeile, Umbruch (Phase 5) ----------
|
||||
% Farben aus der Titelseiten-Palette (bilder/titelseite.svg), damit das PDF
|
||||
% und das Deckblatt als ein zusammenhaengendes System wirken.
|
||||
\usepackage{xcolor}
|
||||
\definecolor{ORindigo}{HTML}{4338CA}
|
||||
\definecolor{ORcyan}{HTML}{0891B2}
|
||||
\definecolor{ORviolett}{HTML}{7C3AED}
|
||||
\definecolor{ORslate}{HTML}{1A1A2E}
|
||||
|
||||
% --- Witwen-/Waisenstrafen (e-TeX) ---
|
||||
% Schusterjungen (erste Zeile eines Absatzes allein am Seitenende) und
|
||||
% Hurenkinder (letzte Zeile allein am Seitenanfang) weitgehend unterdruecken.
|
||||
% \widows=1/\orphans=1 waeren zusaetzlich, sind aber in der article-Klasse
|
||||
% nicht definiert (KOMA/widows-Paket-Syntax) - \widowpenalty/\clubpenalty
|
||||
% reichen als Strafen.
|
||||
\widowpenalty=10000
|
||||
\clubpenalty=10000
|
||||
|
||||
% --- titlesec: modernere Ueberschriften + keine Waisenueberschriften ---
|
||||
% Option nobottomtitles: rutscht eine Ueberschrift ans Seitenende, ohne dass
|
||||
% noch ausreichend Folgeplatz (Default: ~2\baselineskip) bleibt, wird sie auf
|
||||
% die naechste Seite geschoben. explicit: ermoeglicht #1 (Titeltext) im after-
|
||||
% code, um \sectionmark fuer die Kopfzeile manuell zu setzen (noetig, weil
|
||||
% Pandoc \section* verwendet, das \rightmark nicht automatisch befuellt).
|
||||
\usepackage[explicit,nobottomtitles]{titlesec}
|
||||
\titleformat{\section}[block]
|
||||
{\Large\bfseries\color{ORindigo}}
|
||||
{}{0pt}
|
||||
{#1}
|
||||
[\vspace{2pt}{\color{ORviolett}\titlerule[1.2pt]}\vspace{2pt}\sectionmark{#1}]
|
||||
\titleformat{\subsection}[block]
|
||||
{\large\bfseries\color{ORcyan}}
|
||||
{}{0pt}
|
||||
{#1}[\subsectionmark{#1}]
|
||||
\titleformat{\subsubsection}[block]
|
||||
{\normalsize\bfseries\color{ORslate}}
|
||||
{}{0pt}
|
||||
{#1}
|
||||
\titlespacing*{\section}{0pt}{2.5ex plus 1ex minus .2ex}{1.2ex plus .2ex}
|
||||
\titlespacing*{\subsection}{0pt}{2ex plus .8ex}{1ex plus .2ex}
|
||||
\titlespacing*{\subsubsection}{0pt}{1.5ex plus .6ex}{0.8ex plus .1ex}
|
||||
|
||||
% --- Sicherungsnetz: needspace fuer den Fall, dass nobottomtitles greift ---
|
||||
% titlesec hat \section bereits umdefiniert; \preto haengt \needspace vorne an,
|
||||
% wirkt also auf \section und \section* gleichermaessen. 3\baselineskip
|
||||
% garantiert mindestens 3 Folgezeilen unter der Ueberschrift.
|
||||
\usepackage{needspace}
|
||||
\usepackage{etoolbox}
|
||||
\preto\section{\needspace{3\baselineskip}}
|
||||
\preto\subsection{\needspace{2.5\baselineskip}}
|
||||
\preto\subsubsection{\needspace{2\baselineskip}}
|
||||
|
||||
% --- Kopf-/Fusszeile (fancyhdr) ---
|
||||
% article ist einseitig (oneside) -> es gibt nur L/R, kein Innen/Aussen.
|
||||
% \rightmark = aktueller Abschnittstitel (article: \sectionmark -> \markright).
|
||||
% Wir setzen \sectionmark explizit via titlesec-after-code, weil \section* den
|
||||
% Mark nicht selbst befuellt. \nouppercase laesst Gross-/Kleinschreibung intakt.
|
||||
\usepackage{fancyhdr}
|
||||
% Kopfzeile braucht ~22pt Hoehe (fancyhdr warnt sonst); topmargin anpassen,
|
||||
% damit der Textkörper nicht nach unten wandert.
|
||||
\setlength{\headheight}{22pt}
|
||||
\addtolength{\topmargin}{-10pt}
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\fancyhead[L]{\small\nouppercase{\rightmark}}
|
||||
\fancyhead[R]{\small\thepage}
|
||||
\fancyfoot{}
|
||||
\renewcommand{\headrulewidth}{0.4pt}
|
||||
\renewcommand{\headrule}{%
|
||||
\hbox to\headwidth{\color{ORviolett}\leaders\hrule height\headrulewidth\hfill}}
|
||||
% plain = erste Seite eines (mit \clearpage begonnenen) Kapitels: nur Seitenzahl.
|
||||
\fancypagestyle{plain}{%
|
||||
\fancyhf{}\fancyfoot[C]{\small\thepage}\renewcommand{\headrulewidth}{0pt}}
|
||||
|
||||
% --- Titelseite (Seite 1, reines Bild) bekommt keine Kopfzeile ---
|
||||
% Ohne diese Zeile erbt Seite 1 den globalen fancy-Stil (Trennlinie +
|
||||
% Seitenzahl oben rechts), was auf dem Deckblatt fehl am Platz ist.
|
||||
\AtBeginDocument{\thispagestyle{plain}}
|
||||
1
bilder_04/plotly/kap06_jobshop_gantt.html
Normal file
1
bilder_04/plotly/kap_markowitz_restriktionen.html
Normal file
1
bilder_04/plotly/kap_mehrziel_pareto.html
Normal file
1
bilder_04/plotly/kap_metaheuristiken_verlauf.html
Normal file
BIN
bilder_04/teil2_solverwahl.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
163
bilder_04/teil2_solverwahl.svg
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="740pt" height="532pt"
|
||||
viewBox="0.00 0.00 740.00 532.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 527.75)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="365.89" y="-507.4" font-family="DejaVu Sans" font-size="13.00">Welches Werkzeug passt zu meinem Problem? (Teil II)</text>
|
||||
<!-- f1 -->
|
||||
<g id="node1" class="node">
|
||||
<title>f1</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="538.52,-499.25 434,-470.87 538.52,-442.49 643.04,-470.87 538.52,-499.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="538.52" y="-474.12" font-family="DejaVu Sans" font-size="10.00">Fahrzeuge, Depots,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="538.52" y="-461.37" font-family="DejaVu Sans" font-size="10.00">Zeitfenster?</text>
|
||||
</g>
|
||||
<!-- f2 -->
|
||||
<g id="node2" class="node">
|
||||
<title>f2</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="422.52,-399.24 270.75,-370.86 422.52,-342.48 574.29,-370.86 422.52,-399.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="422.52" y="-374.11" font-family="DejaVu Sans" font-size="10.00">Ja/Nein-Entscheidungen,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="422.52" y="-361.36" font-family="DejaVu Sans" font-size="10.00">Zuordnungen, Reihenfolgen?</text>
|
||||
</g>
|
||||
<!-- f1->f2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>f1->f2</title>
|
||||
<path fill="none" stroke="#5c6380" d="M513.35,-448.6C497.2,-434.96 475.99,-417.04 458.13,-401.95"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="460.72,-399.55 450.82,-395.77 456.2,-404.9 460.72,-399.55"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="493.75" y="-417.94" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- routing -->
|
||||
<g id="node6" class="node">
|
||||
<title>routing</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M696.16,-390.09C696.16,-390.09 610.88,-390.09 610.88,-390.09 604.88,-390.09 598.88,-384.09 598.88,-378.09 598.88,-378.09 598.88,-363.63 598.88,-363.63 598.88,-357.63 604.88,-351.63 610.88,-351.63 610.88,-351.63 696.16,-351.63 696.16,-351.63 702.16,-351.63 708.16,-357.63 708.16,-363.63 708.16,-363.63 708.16,-378.09 708.16,-378.09 708.16,-384.09 702.16,-390.09 696.16,-390.09"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="653.52" y="-374.11" font-family="DejaVu Sans" font-size="10.00">OR-Tools Routing</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="653.52" y="-361.36" font-family="DejaVu Sans" font-size="10.00">(Kapitel Graphen)</text>
|
||||
</g>
|
||||
<!-- f1->routing -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>f1->routing</title>
|
||||
<path fill="none" stroke="#5c6380" d="M563.47,-448.6C580.84,-433.8 604.13,-413.96 622.66,-398.16"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="624.84,-400.9 630.18,-391.75 620.3,-395.57 624.84,-400.9"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="603.22" y="-417.94" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- f3 -->
|
||||
<g id="node3" class="node">
|
||||
<title>f3</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="264.52,-286.48 122.5,-258.1 264.52,-229.72 406.54,-258.1 264.52,-286.48"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="264.52" y="-261.35" font-family="DejaVu Sans" font-size="10.00">Zielfunktion und alle</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="264.52" y="-248.6" font-family="DejaVu Sans" font-size="10.00">Nebenbedingungen linear?</text>
|
||||
</g>
|
||||
<!-- f2->f3 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>f2->f3</title>
|
||||
<path fill="none" stroke="#5c6380" d="M390.93,-347.71C366.19,-330.38 331.63,-306.14 304.89,-287.4"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="307.23,-284.76 297.03,-281.89 303.21,-290.5 307.23,-284.76"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="367.42" y="-317.93" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- f4 -->
|
||||
<g id="node4" class="node">
|
||||
<title>f4</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="581.52,-299.23 431.25,-258.1 581.52,-216.97 731.79,-258.1 581.52,-299.23"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="581.52" y="-267.73" font-family="DejaVu Sans" font-size="10.00">Logische Regeln</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="581.52" y="-254.98" font-family="DejaVu Sans" font-size="10.00">(wenn-dann, entweder-oder)</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="581.52" y="-242.23" font-family="DejaVu Sans" font-size="10.00">oder Zeitplanung?</text>
|
||||
</g>
|
||||
<!-- f2->f4 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>f2->f4</title>
|
||||
<path fill="none" stroke="#5c6380" d="M454.31,-347.71C476.01,-332.6 505.23,-312.24 530.19,-294.85"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="532.08,-297.8 538.29,-289.22 528.08,-292.06 532.08,-297.8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="501" y="-317.93" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- f5 -->
|
||||
<g id="node5" class="node">
|
||||
<title>f5</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="134.52,-151.22 0,-122.84 134.52,-94.46 269.04,-122.84 134.52,-151.22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="134.52" y="-126.09" font-family="DejaVu Sans" font-size="10.00">Nur Fluss oder</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="134.52" y="-113.34" font-family="DejaVu Sans" font-size="10.00">Zuordnung im Netzwerk?</text>
|
||||
</g>
|
||||
<!-- f3->f5 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>f3->f5</title>
|
||||
<path fill="none" stroke="#5c6380" d="M241.58,-233.58C220.32,-211.79 188.55,-179.22 165.14,-155.22"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="167.78,-152.92 158.29,-148.21 162.77,-157.81 167.78,-152.92"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="211.05" y="-181.17" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- teil3 -->
|
||||
<g id="node11" class="node">
|
||||
<title>teil3</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M379.16,-140.84C379.16,-140.84 305.88,-140.84 305.88,-140.84 299.88,-140.84 293.88,-134.84 293.88,-128.84 293.88,-128.84 293.88,-116.84 293.88,-116.84 293.88,-110.84 299.88,-104.84 305.88,-104.84 305.88,-104.84 379.16,-104.84 379.16,-104.84 385.16,-104.84 391.16,-110.84 391.16,-116.84 391.16,-116.84 391.16,-128.84 391.16,-128.84 391.16,-134.84 385.16,-140.84 379.16,-140.84"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="342.52" y="-119.72" font-family="DejaVu Sans" font-size="10.00">weiter in Teil III</text>
|
||||
</g>
|
||||
<!-- f3->teil3 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>f3->teil3</title>
|
||||
<path fill="none" stroke="#5c6380" d="M279.19,-232.03C292.65,-209.05 312.53,-175.07 326.42,-151.35"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="329.36,-153.26 331.39,-142.86 323.31,-149.72 329.36,-153.26"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="324.81" y="-181.17" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- cpsat -->
|
||||
<g id="node7" class="node">
|
||||
<title>cpsat</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M532.16,-142.07C532.16,-142.07 452.88,-142.07 452.88,-142.07 446.88,-142.07 440.88,-136.07 440.88,-130.07 440.88,-130.07 440.88,-115.61 440.88,-115.61 440.88,-109.61 446.88,-103.61 452.88,-103.61 452.88,-103.61 532.16,-103.61 532.16,-103.61 538.16,-103.61 544.16,-109.61 544.16,-115.61 544.16,-115.61 544.16,-130.07 544.16,-130.07 544.16,-136.07 538.16,-142.07 532.16,-142.07"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="492.52" y="-126.09" font-family="DejaVu Sans" font-size="10.00">CP-SAT</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="492.52" y="-113.34" font-family="DejaVu Sans" font-size="10.00">(Kapitel CP-SAT)</text>
|
||||
</g>
|
||||
<!-- f4->cpsat -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>f4->cpsat</title>
|
||||
<path fill="none" stroke="#5c6380" d="M558.6,-222.78C543.99,-200.91 525.3,-172.91 511.52,-152.29"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="514.5,-150.44 506.03,-144.07 508.67,-154.33 514.5,-150.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="546.1" y="-181.17" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- milp -->
|
||||
<g id="node8" class="node">
|
||||
<title>milp</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M685.54,-142.07C685.54,-142.07 581.5,-142.07 581.5,-142.07 575.5,-142.07 569.5,-136.07 569.5,-130.07 569.5,-130.07 569.5,-115.61 569.5,-115.61 569.5,-109.61 575.5,-103.61 581.5,-103.61 581.5,-103.61 685.54,-103.61 685.54,-103.61 691.54,-103.61 697.54,-109.61 697.54,-115.61 697.54,-115.61 697.54,-130.07 697.54,-130.07 697.54,-136.07 691.54,-142.07 685.54,-142.07"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="633.52" y="-126.09" font-family="DejaVu Sans" font-size="10.00">MILP: HiGHS / CP-SAT</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="633.52" y="-113.34" font-family="DejaVu Sans" font-size="10.00">(Kapitel MILP)</text>
|
||||
</g>
|
||||
<!-- f4->milp -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>f4->milp</title>
|
||||
<path fill="none" stroke="#5c6380" d="M595.87,-220.33C604.1,-199.24 614.29,-173.11 622,-153.37"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="625.18,-154.85 625.55,-144.26 618.66,-152.3 625.18,-154.85"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="642.97" y="-192.42" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="642.97" y="-181.17" font-family="DejaVu Sans" font-size="9.00">(Fixkosten,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="642.97" y="-169.92" font-family="DejaVu Sans" font-size="9.00">Kardinalität)</text>
|
||||
</g>
|
||||
<!-- netz -->
|
||||
<g id="node9" class="node">
|
||||
<title>netz</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M116.29,-51.21C116.29,-51.21 16.75,-51.21 16.75,-51.21 10.75,-51.21 4.75,-45.21 4.75,-39.21 4.75,-39.21 4.75,-12 4.75,-12 4.75,-6 10.75,0 16.75,0 16.75,0 116.29,0 116.29,0 122.29,0 128.29,-6 128.29,-12 128.29,-12 128.29,-39.21 128.29,-39.21 128.29,-45.21 122.29,-51.21 116.29,-51.21"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="66.52" y="-35.23" font-family="DejaVu Sans" font-size="10.00">Spezialalgorithmus:</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="66.52" y="-22.48" font-family="DejaVu Sans" font-size="10.00">Min-Cost-Flow,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="66.52" y="-9.73" font-family="DejaVu Sans" font-size="10.00">Ungarische Methode</text>
|
||||
</g>
|
||||
<!-- f5->netz -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>f5->netz</title>
|
||||
<path fill="none" stroke="#5c6380" d="M117.36,-97.81C109.4,-86.66 99.78,-73.19 91.07,-60.99"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="94.16,-59.3 85.51,-53.19 88.47,-63.37 94.16,-59.3"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="106.31" y="-69.91" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- lp -->
|
||||
<g id="node10" class="node">
|
||||
<title>lp</title>
|
||||
<path fill="#eef2ff" stroke="#4338ca" stroke-width="2" d="M242.04,-51.21C242.04,-51.21 165,-51.21 165,-51.21 159,-51.21 153,-45.21 153,-39.21 153,-39.21 153,-12 153,-12 153,-6 159,0 165,0 165,0 242.04,0 242.04,0 248.04,0 254.04,-6 254.04,-12 254.04,-12 254.04,-39.21 254.04,-39.21 254.04,-45.21 248.04,-51.21 242.04,-51.21"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="203.52" y="-35.23" font-family="DejaVu Sans" font-size="10.00">LP: scipy.linprog</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="203.52" y="-22.48" font-family="DejaVu Sans" font-size="10.00">oder highspy</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="203.52" y="-9.73" font-family="DejaVu Sans" font-size="10.00">(Kapitel LP)</text>
|
||||
</g>
|
||||
<!-- f5->lp -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>f5->lp</title>
|
||||
<path fill="none" stroke="#5c6380" d="M151.93,-97.81C160.01,-86.66 169.77,-73.19 178.61,-60.99"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="181.23,-63.34 184.26,-53.19 175.56,-59.23 181.23,-63.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="182.9" y="-69.91" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
BIN
bilder_04/teil3_solverwahl.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
163
bilder_04/teil3_solverwahl.svg
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.2 (20260126.1125)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="990pt" height="408pt"
|
||||
viewBox="0.00 0.00 990.00 408.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 403.74)">
|
||||
<text xml:space="preserve" text-anchor="middle" x="490.89" y="-383.39" font-family="DejaVu Sans" font-size="13.00">Welches Werkzeug passt zu meinem Problem? (Teil III)</text>
|
||||
<!-- g1 -->
|
||||
<g id="node1" class="node">
|
||||
<title>g1</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="510.89,-375.24 395.88,-346.86 510.89,-318.48 625.91,-346.86 510.89,-375.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="510.89" y="-350.11" font-family="DejaVu Sans" font-size="10.00">Sind die Daten sicher</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="510.89" y="-337.36" font-family="DejaVu Sans" font-size="10.00">bekannt?</text>
|
||||
</g>
|
||||
<!-- g2 -->
|
||||
<g id="node2" class="node">
|
||||
<title>g2</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="370.89,-275.23 259.62,-234.1 370.89,-192.97 482.16,-234.1 370.89,-275.23"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="370.89" y="-243.72" font-family="DejaVu Sans" font-size="10.00">Entscheidung fällt in</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="370.89" y="-230.97" font-family="DejaVu Sans" font-size="10.00">mehreren Stufen</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="370.89" y="-218.22" font-family="DejaVu Sans" font-size="10.00">über die Zeit?</text>
|
||||
</g>
|
||||
<!-- g1->g2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>g1->g2</title>
|
||||
<path fill="none" stroke="#5c6380" d="M483.9,-324.5C464.33,-309.02 437.44,-287.74 414.8,-269.83"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="417.23,-267.3 407.22,-263.84 412.89,-272.79 417.23,-267.3"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="463.18" y="-293.93" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- g4 -->
|
||||
<g id="node4" class="node">
|
||||
<title>g4</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="651.89,-275.23 506.88,-234.1 651.89,-192.97 796.91,-234.1 651.89,-275.23"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="651.89" y="-243.72" font-family="DejaVu Sans" font-size="10.00">Zielfunktion konvex?</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="651.89" y="-230.97" font-family="DejaVu Sans" font-size="10.00">(Quadrate, Normen,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="651.89" y="-218.22" font-family="DejaVu Sans" font-size="10.00">log/exp konvex kombiniert)</text>
|
||||
</g>
|
||||
<!-- g1->g4 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>g1->g4</title>
|
||||
<path fill="none" stroke="#5c6380" d="M538.08,-324.5C556.93,-309.7 582.53,-289.59 604.67,-272.2"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="606.71,-275.05 612.41,-266.12 602.38,-269.54 606.71,-275.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="580.92" y="-293.93" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- g3 -->
|
||||
<g id="node3" class="node">
|
||||
<title>g3</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="155.89,-149.72 40.88,-121.34 155.89,-92.96 270.91,-121.34 155.89,-149.72"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="155.89" y="-124.59" font-family="DejaVu Sans" font-size="10.00">Wahrscheinlichkeiten</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="155.89" y="-111.84" font-family="DejaVu Sans" font-size="10.00">bekannt?</text>
|
||||
</g>
|
||||
<!-- g2->g3 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>g2->g3</title>
|
||||
<path fill="none" stroke="#5c6380" d="M324.77,-209.34C288.68,-190.75 238.71,-165 202.5,-146.35"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="204.51,-143.45 194.02,-141.98 201.3,-149.67 204.51,-143.45"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="266.7" y="-168.42" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- dp -->
|
||||
<g id="node6" class="node">
|
||||
<title>dp</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M452.16,-140.57C452.16,-140.57 307.62,-140.57 307.62,-140.57 301.62,-140.57 295.62,-134.57 295.62,-128.57 295.62,-128.57 295.62,-114.11 295.62,-114.11 295.62,-108.11 301.62,-102.11 307.62,-102.11 307.62,-102.11 452.16,-102.11 452.16,-102.11 458.16,-102.11 464.16,-108.11 464.16,-114.11 464.16,-114.11 464.16,-128.57 464.16,-128.57 464.16,-134.57 458.16,-140.57 452.16,-140.57"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="379.89" y="-124.59" font-family="DejaVu Sans" font-size="10.00">Dynamische Programmierung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="379.89" y="-111.84" font-family="DejaVu Sans" font-size="10.00">(Bellman)</text>
|
||||
</g>
|
||||
<!-- g2->dp -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>g2->dp</title>
|
||||
<path fill="none" stroke="#5c6380" d="M374.12,-193.43C375.21,-179.97 376.41,-165.17 377.44,-152.57"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="380.91,-153.05 378.23,-142.8 373.93,-152.48 380.91,-153.05"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="379.95" y="-168.42" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- stoch -->
|
||||
<g id="node7" class="node">
|
||||
<title>stoch</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M137.79,-38.46C137.79,-38.46 12,-38.46 12,-38.46 6,-38.46 0,-32.46 0,-26.46 0,-26.46 0,-12 0,-12 0,-6 6,0 12,0 12,0 137.79,0 137.79,0 143.79,0 149.79,-6 149.79,-12 149.79,-12 149.79,-26.46 149.79,-26.46 149.79,-32.46 143.79,-38.46 137.79,-38.46"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="74.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">Zweistufige stochastische</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="74.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">Optimierung, Szenarien</text>
|
||||
</g>
|
||||
<!-- g3->stoch -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>g3->stoch</title>
|
||||
<path fill="none" stroke="#5c6380" d="M136.9,-96.87C125.02,-82.18 109.66,-63.19 97.2,-47.79"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="100.21,-45.95 91.2,-40.38 94.76,-50.35 100.21,-45.95"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="123.99" y="-62.79" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- robust -->
|
||||
<g id="node8" class="node">
|
||||
<title>robust</title>
|
||||
<path fill="#f5f3ff" stroke="#7c3aed" stroke-width="2" d="M289.16,-38.46C289.16,-38.46 186.62,-38.46 186.62,-38.46 180.62,-38.46 174.62,-32.46 174.62,-26.46 174.62,-26.46 174.62,-12 174.62,-12 174.62,-6 180.62,0 186.62,0 186.62,0 289.16,0 289.16,0 295.16,0 301.16,-6 301.16,-12 301.16,-12 301.16,-26.46 301.16,-26.46 301.16,-32.46 295.16,-38.46 289.16,-38.46"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="237.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">Robuste Optimierung</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="237.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">(Worst Case)</text>
|
||||
</g>
|
||||
<!-- g3->robust -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>g3->robust</title>
|
||||
<path fill="none" stroke="#5c6380" d="M174.91,-97.12C186.94,-82.44 202.54,-63.39 215.21,-47.93"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="217.69,-50.42 221.32,-40.47 212.28,-45.98 217.69,-50.42"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246.16" y="-68.41" font-family="DejaVu Sans" font-size="9.00">nein,</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="246.16" y="-57.16" font-family="DejaVu Sans" font-size="9.00">nur Bandbreiten</text>
|
||||
</g>
|
||||
<!-- g5 -->
|
||||
<g id="node5" class="node">
|
||||
<title>g5</title>
|
||||
<polygon fill="#eef2ff" stroke="#4338ca" stroke-width="1.6" points="646.89,-149.72 492.88,-121.34 646.89,-92.96 800.91,-121.34 646.89,-149.72"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="646.89" y="-124.59" font-family="DejaVu Sans" font-size="10.00">Quadratisch mit</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="646.89" y="-111.84" font-family="DejaVu Sans" font-size="10.00">linearen Nebenbedingungen?</text>
|
||||
</g>
|
||||
<!-- g4->g5 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>g4->g5</title>
|
||||
<path fill="none" stroke="#5c6380" d="M650.08,-192.8C649.61,-182.57 649.12,-171.61 648.66,-161.44"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="652.16,-161.32 648.21,-151.48 645.17,-161.63 652.16,-161.32"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="653" y="-168.42" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- nichtkonvex -->
|
||||
<g id="node11" class="node">
|
||||
<title>nichtkonvex</title>
|
||||
<path fill="#fffbeb" stroke="#b45309" stroke-width="2" d="M969.79,-146.94C969.79,-146.94 838,-146.94 838,-146.94 832,-146.94 826,-140.94 826,-134.94 826,-134.94 826,-107.73 826,-107.73 826,-101.73 832,-95.73 838,-95.73 838,-95.73 969.79,-95.73 969.79,-95.73 975.79,-95.73 981.79,-101.73 981.79,-107.73 981.79,-107.73 981.79,-134.94 981.79,-134.94 981.79,-140.94 975.79,-146.94 969.79,-146.94"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="903.89" y="-130.97" font-family="DejaVu Sans" font-size="10.00">scipy.optimize.minimize</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="903.89" y="-118.22" font-family="DejaVu Sans" font-size="10.00">mit mehreren Startpunkten</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="903.89" y="-105.47" font-family="DejaVu Sans" font-size="10.00">— nur lokales Optimum!</text>
|
||||
</g>
|
||||
<!-- g4->nichtkonvex -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>g4->nichtkonvex</title>
|
||||
<path fill="none" stroke="#5c6380" d="M708.45,-208.24C746.01,-191.73 795.56,-169.96 835.36,-152.46"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="836.65,-155.72 844.4,-148.49 833.83,-149.31 836.65,-155.72"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="810.22" y="-168.42" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
<!-- qp -->
|
||||
<g id="node9" class="node">
|
||||
<title>qp</title>
|
||||
<path fill="#ecfeff" stroke="#0891b2" stroke-width="2" d="M611.91,-38.46C611.91,-38.46 531.88,-38.46 531.88,-38.46 525.88,-38.46 519.88,-32.46 519.88,-26.46 519.88,-26.46 519.88,-12 519.88,-12 519.88,-6 525.88,0 531.88,0 531.88,0 611.91,0 611.91,0 617.91,0 623.91,-6 623.91,-12 623.91,-12 623.91,-26.46 623.91,-26.46 623.91,-32.46 617.91,-38.46 611.91,-38.46"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="571.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">QP über CVXPY</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="571.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">(OSQP, Clarabel)</text>
|
||||
</g>
|
||||
<!-- g5->qp -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>g5->qp</title>
|
||||
<path fill="none" stroke="#5c6380" d="M628.36,-95.59C617.68,-81.34 604.22,-63.38 593.13,-48.57"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="595.93,-46.47 587.13,-40.57 590.33,-50.67 595.93,-46.47"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="617.64" y="-62.79" font-family="DejaVu Sans" font-size="9.00">ja</text>
|
||||
</g>
|
||||
<!-- konvex -->
|
||||
<g id="node10" class="node">
|
||||
<title>konvex</title>
|
||||
<path fill="#ecfdf5" stroke="#059669" stroke-width="2" d="M782.91,-38.46C782.91,-38.46 660.88,-38.46 660.88,-38.46 654.88,-38.46 648.88,-32.46 648.88,-26.46 648.88,-26.46 648.88,-12 648.88,-12 648.88,-6 654.88,0 660.88,0 660.88,0 782.91,0 782.91,0 788.91,0 794.91,-6 794.91,-12 794.91,-12 794.91,-26.46 794.91,-26.46 794.91,-32.46 788.91,-38.46 782.91,-38.46"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="721.89" y="-22.48" font-family="DejaVu Sans" font-size="10.00">CVXPY</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="721.89" y="-9.73" font-family="DejaVu Sans" font-size="10.00">— prüft Konvexität selbst</text>
|
||||
</g>
|
||||
<!-- g5->konvex -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>g5->konvex</title>
|
||||
<path fill="none" stroke="#5c6380" d="M665.43,-95.59C676.11,-81.34 689.57,-63.38 700.66,-48.57"/>
|
||||
<polygon fill="#5c6380" stroke="#5c6380" points="703.46,-50.67 706.66,-40.57 697.86,-46.47 703.46,-50.67"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="705.25" y="-62.79" font-family="DejaVu Sans" font-size="9.00">nein</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
BIN
bilder_04/titelseite.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
2812
bilder_04/titelseite.svg
Normal file
|
After Width: | Height: | Size: 98 KiB |