operations_research/bilder_04/stil_04.py

92 lines
3.4 KiB
Python
Raw Normal View History

Grafik-Upgrade Stufe 1: elf neue Diagramme Grafik_Upgrade.md verlangt "37 didaktisch wertvolle Visualisierungen". Es sind 19: Die Abschnitte "Modul 1-4" wiederholen die Grafiken 1-18 unter den Nummern 19-36, nur Grafik 37 ist zusaetzlich. Belegt an den Skriptnamen - 38 Nennungen, 19 verschiedene Dateien, erzeuge_kombinatorik_wand.py steht dreimal darin. Von den 19 betreffen neun bereits vorhandene Diagramme. Umgesetzt sind die zehn wirklich neuen plus eine elfte, die aus einem Sachfehler des Auftrags entstand: Grafik 14 sollte in Abschnitt 12.3 stehen, nannte aber Zahlen aus Kapitel 17. Beide Stellen haben jetzt ihre eigene Grafik mit ihren eigenen Zahlen. Vier Vorgaben des Auftrags wurden bewusst nicht befolgt: PNG bei 300 dpi (das Buch kennt nur SVG - die 33 verwaisten PNGs waren gerade erst geloescht worden), Matplotlib-Standardfarben (die Buchpalette steht in 33 Diagrammen), harte Kapitelnummern im Fliesstext ({ref:sec:...} stattdessen) und die fehlende Byte-Reproduzierbarkeit. Der eigentliche Ertrag waren die Selbsttests. Jeder Generator prueft seine Zahlen gegen das, was im Kapitel steht, und bricht bei Abweichung ab. Vier haben angeschlagen: * Kombinatorik-Wand: 25! ergibt 491,5 Mio. Jahre, die Tabelle nennt 490 Mio. - Rundung auf zwei signifikante Stellen, nicht Fehler des Buchs. * Laufzeit-Diagramm: Die abgedruckte Prozentspalte laesst sich aus den abgedruckten Zeiten nicht exakt nachrechnen (0,001/0,008 ergibt 12 %, gedruckt sind 14 %) - die Zeiten sind gerundet, die Prozente nicht. * Predict-then-Optimize: geratene Datenaufteilung ergab 197,35 statt 200,95 MSE. Mit TRAINING aus dem Buchprogramm stimmen alle vier Kostenwerte. * Almgren-Chriss: ein echter Fehler im Buchprogramm. DER FUND: analytische_loesung() minimiert die eigene Kostenfunktion nicht. Ihr Pfad kostet 10.860,40 EUR, der DP-Pfad 10.266,24 EUR - ein Gitterverfahren kann das kontinuierliche Optimum aber nicht unterbieten. periodenkosten() rechnet das Risiko mit lambda/2, die geschlossene Formel setzt kappa~^2 = lambda*sigma^2*P0^2/eta ohne dieses Halbe. Mit lambda/2 liefert die Formel 10.264,85 EUR und denselben Pfad wie die DP, knapp UNTER der Gitterloesung. Die Grafik zeigt bis zur Entscheidung darueber den DP-Pfad; er ist der, den das Kapitel abdruckt. Nachweise: Alle elf Diagramme sind ueber zwei Laeufe byte-identisch. Kein vorhandenes Diagramm wurde veraendert. Die Lastabwurf-Grafik reproduziert die Befundtabelle exakt (874.870/2.504.154/28 von 40 gegen 351.356/415.924/0). Zwei Abbildungen landeten beim automatischen Einfuegen im Codeblock (ihre Anker standen in abgedruckten Ausgaben) und wurden dahinter verschoben; eine Pruefung ueber alle elf zeigt null Fehlplatzierungen. Neu: bilder_04/stil_04.py buendelt Palette, rcParams und speichere(). networkx wurde entgegen der Planung nicht gebraucht - pyproject.toml bleibt unveraendert. PDF von 781 auf 788 Seiten, 44 Diagramme, 27 Generatoren. Veroeffentlicht: 25 Dateien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 01:11:33 +02:00
#!/usr/bin/env python3
# stil_04.py
"""
Gemeinsamer Unterbau der Diagramm-Generatoren: Farben, Grundeinstellungen und
das Schreiben der SVG-Datei.
Warum es das gibt: Elf neue Generatoren trugen sonst elfmal denselben Vorspann.
Eine Zeile daraus ist besonders heikel -
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
Ohne sie wuerfelt matplotlib die clip-path-IDs je Figure neu; die Datei aendert
sich dann bei JEDEM Lauf, ohne dass sich am Bild etwas geaendert haette. Das
faellt nicht sofort auf, sondern erst als unlesbarer git-diff - und dann sucht
man die Ursache lange. Dasselbe gilt fuer metadata={"Date": None} beim
Speichern: Sonst steht der Schreibzeitstempel auf die Mikrosekunde im SVG.
Die 33 aelteren Generatoren setzen beides jeweils selbst. Sie bleiben
unveraendert - eine Umstellung waere ein eigener Schritt und braeuchte den
Nachweis, dass jedes einzelne Bild byte-identisch bleibt.
Benutzung:
from stil_04 import FARBEN, neue_figur, speichere
figur, achse = neue_figur(breite=7.6, hoehe=4.6)
...
speichere(figur, "kap_einfuehrung_kombinatorik")
"""
from __future__ import annotations
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
# Reproduzierbare SVG-Ausgabe - siehe Modulkommentar.
plt.rcParams["svg.hashsalt"] = "or-mit-python-v04"
# Serifenlos und ueberall vorhanden. Bewusst kein Stylesheet von seaborn: Das
# Buch hat eine eigene Palette, und zwei Stilsysteme nebeneinander sehen nach
# zwei Buechern aus.
plt.rcParams["font.family"] = "DejaVu Sans"
plt.rcParams["axes.grid"] = True
plt.rcParams["grid.linestyle"] = ":"
plt.rcParams["grid.alpha"] = 0.4
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False
BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BILDER = os.path.join(BASIS, "bilder_04")
# Die Palette des Buchs - dieselben Werte wie in den 33 vorhandenen Diagrammen
# und auf der Titelseite. Die Namen sagen, wofuer die Farbe steht, nicht wie
# sie aussieht: So bleibt die Bedeutung erhalten, falls ein Ton je gewechselt
# wird.
FARBEN = {
"haupt": "#4338ca", # Indigo - die primaere Groesse, Zielfunktion
"zweit": "#0891b2", # Teal - die Vergleichsgroesse
"warnung": "#b45309", # Amber - teuer, aber zulaessig
"gut": "#059669", # Emerald - Optimum, zulaessige Menge
"fehler": "#be123c", # Rose - unzulaessig, Verlust, Bruch
"akzent": "#7c3aed", # Violett - Schranken, Niveaulinien
"text": "#334155", # Slate - Beschriftung
"gedaempft": "#64748b", # Slate hell - Nebeninformation
"linie": "#94a3b8", # Hilfslinien
"flaeche": "#f1f5f9", # Hinterlegte Flaechen
}
def neue_figur(breite: float = 7.6, hoehe: float = 4.6, **kwargs):
"""Figure und Achse in der Standardgroesse des Buchs."""
return plt.subplots(figsize=(breite, hoehe), **kwargs)
def speichere(figur, name: str) -> str:
"""Schreibt bilder_04/<name>.svg und meldet den Pfad.
Nur SVG: Das PDF bindet die Bilder ueber \\includesvg (inkscape) ein, und
PNG-Zweitfassungen gab es in diesem Projekt schon einmal - sie wurden von
nirgendwo referenziert und mussten wieder geloescht werden."""
figur.tight_layout()
os.makedirs(BILDER, exist_ok=True)
pfad = os.path.join(BILDER, f"{name}.svg")
figur.savefig(pfad, format="svg", dpi=160, bbox_inches="tight",
metadata={"Date": None})
plt.close(figur)
print(f"geschrieben: {pfad}")
return pfad