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>
This commit is contained in:
commit
b7af2f1d9a
468 changed files with 262141 additions and 0 deletions
159
bilder_04/erzeuge_architektur_diagramme.py
Normal file
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()
|
||||
Loading…
Reference in a new issue