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
140
OR_HTML_04/programme/Visualisierung_Loesungsraum.py
Normal file
140
OR_HTML_04/programme/Visualisierung_Loesungsraum.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Visualisierung_Loesungsraum.py
|
||||
"""
|
||||
Kapitel Fundament: Geometrische Visualisierung eines 2D-Optimierungsraums.
|
||||
|
||||
Ecken werden berechnet, auf Zulässigkeit geprüft, bewertet und eingezeichnet.
|
||||
Zusätzlich wird das ganzzahlige Optimum systematisch bestimmt statt behauptet.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use("Agg") # kein Bildschirm nötig
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# --- Modell (identisch zum Kapitel Einfuehrung) ----------------------------
|
||||
# max 150*xA + 250*xB
|
||||
# u.d.N. 2*xA + 5*xB <= 40 (vCPU)
|
||||
# 4*xA + 6*xB <= 60 (RAM)
|
||||
# 1*xA + 0*xB <= 8 (Marktliquidität)
|
||||
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])
|
||||
restriktionsnamen = ["vCPU", "RAM", "Marktlimit"]
|
||||
|
||||
|
||||
def ist_zulaessig(punkt, tol=1e-7):
|
||||
return np.all(A @ punkt <= b + tol) and np.all(punkt >= -tol)
|
||||
|
||||
|
||||
def berechne_ecken():
|
||||
"""
|
||||
Ecken = Schnittpunkte je zweier Begrenzungsgeraden, die zulässig sind.
|
||||
Begrenzungen sind die 3 Restriktionen plus die beiden Achsen xA=0, xB=0.
|
||||
"""
|
||||
geraden = [(A[i], b[i]) for i in range(len(b))]
|
||||
geraden.append((np.array([1.0, 0.0]), 0.0)) # xA = 0
|
||||
geraden.append((np.array([0.0, 1.0]), 0.0)) # xB = 0
|
||||
|
||||
ecken = []
|
||||
for (n1, d1), (n2, d2) in itertools.combinations(geraden, 2):
|
||||
M = np.array([n1, n2])
|
||||
if abs(np.linalg.det(M)) < 1e-9: # parallel -> kein Schnittpunkt
|
||||
continue
|
||||
p = np.linalg.solve(M, np.array([d1, d2]))
|
||||
if ist_zulaessig(p) and not any(np.allclose(p, e) for e in ecken):
|
||||
ecken.append(p)
|
||||
return np.array(ecken)
|
||||
|
||||
|
||||
def bestes_ganzzahliges():
|
||||
"""Vollständige Suche über das kleine Gitter - hier zulässig, weil winzig."""
|
||||
bester_wert, bester_punkt = -np.inf, None
|
||||
for xa in range(0, 21):
|
||||
for xb in range(0, 21):
|
||||
p = np.array([float(xa), float(xb)])
|
||||
if ist_zulaessig(p) and c @ p > bester_wert:
|
||||
bester_wert, bester_punkt = c @ p, p
|
||||
return bester_punkt, bester_wert
|
||||
|
||||
|
||||
# --- Analyse ---------------------------------------------------------------
|
||||
ecken = berechne_ecken()
|
||||
werte = ecken @ c
|
||||
reihenfolge = np.argsort(-werte)
|
||||
|
||||
print("=" * 62)
|
||||
print(" ECKEN DES ZULÄSSIGEN POLYEDERS (nach Zielwert sortiert)")
|
||||
print("=" * 62)
|
||||
print(f"{'x_A':>8} {'x_B':>8} {'Z = 150 xA + 250 xB':>24}")
|
||||
print("-" * 62)
|
||||
for i in reihenfolge:
|
||||
print(f"{ecken[i, 0]:>8.2f} {ecken[i, 1]:>8.2f} {werte[i]:>24,.2f} EUR")
|
||||
|
||||
lp_punkt, lp_wert = ecken[reihenfolge[0]], werte[reihenfolge[0]]
|
||||
ip_punkt, ip_wert = bestes_ganzzahliges()
|
||||
|
||||
print("-" * 62)
|
||||
print(f"Kontinuierliches Optimum (LP): x = ({lp_punkt[0]:.2f}, {lp_punkt[1]:.2f}), "
|
||||
f"Z = {lp_wert:,.2f} EUR")
|
||||
print(f"Ganzzahliges Optimum (IP): x = ({ip_punkt[0]:.0f}, {ip_punkt[1]:.0f}), "
|
||||
f"Z = {ip_wert:,.2f} EUR")
|
||||
print(f"Preis der Ganzzahligkeit: {lp_wert - ip_wert:,.2f} EUR "
|
||||
f"({(1 - ip_wert / lp_wert) * 100:.2f} %)")
|
||||
print("=" * 62)
|
||||
|
||||
# --- Zeichnung -------------------------------------------------------------
|
||||
gitter = np.linspace(0, 15, 400)
|
||||
xa_gitter, xb_gitter = np.meshgrid(gitter, gitter)
|
||||
|
||||
plt.figure(figsize=(10, 8))
|
||||
|
||||
# Restriktionsgeraden
|
||||
plt.plot(gitter, (40 - 2 * gitter) / 5, color="tab:blue", lw=2,
|
||||
label=r"$2x_A + 5x_B \leq 40$ (vCPU)")
|
||||
plt.plot(gitter, (60 - 4 * gitter) / 6, color="tab:green", lw=2,
|
||||
label=r"$4x_A + 6x_B \leq 60$ (RAM)")
|
||||
plt.axvline(x=8, color="tab:orange", lw=2, label=r"$x_A \leq 8$ (Marktlimit)")
|
||||
|
||||
# Zulässiger Bereich
|
||||
maske = ((2 * xa_gitter + 5 * xb_gitter <= 40) & (4 * xa_gitter + 6 * xb_gitter <= 60)
|
||||
& (xa_gitter <= 8) & (xa_gitter >= 0) & (xb_gitter >= 0))
|
||||
plt.imshow(maske.astype(int), extent=(0, 15, 0, 15), origin="lower",
|
||||
cmap="Greys", alpha=0.25, aspect="auto")
|
||||
|
||||
# Höhenlinien der Zielfunktion
|
||||
Z = 150 * xa_gitter + 250 * xb_gitter
|
||||
hoehen = plt.contour(xa_gitter, xb_gitter, Z, levels=[500, 1000, 1500, 2000, 2375],
|
||||
colors="purple", linestyles="--", alpha=0.7)
|
||||
plt.clabel(hoehen, inline=True, fontsize=9, fmt="Z = %1.0f EUR")
|
||||
|
||||
# Ecken einzeichnen - jetzt werden sie tatsächlich benutzt
|
||||
plt.scatter(ecken[:, 0], ecken[:, 1], s=70, facecolors="white",
|
||||
edgecolors="black", zorder=4, label="Ecken des Polyeders")
|
||||
for e, w in zip(ecken, werte):
|
||||
plt.annotate(f"({e[0]:.1f}, {e[1]:.1f})\nZ={w:,.0f}", (e[0], e[1]),
|
||||
textcoords="offset points", xytext=(6, 6), fontsize=8)
|
||||
|
||||
plt.scatter([lp_punkt[0]], [lp_punkt[1]], color="purple", marker="D", s=110, zorder=5,
|
||||
label=f"LP-Optimum ({lp_punkt[0]:.1f}, {lp_punkt[1]:.1f})")
|
||||
plt.scatter([ip_punkt[0]], [ip_punkt[1]], color="red", s=170, zorder=6,
|
||||
label=f"Ganzzahliges Optimum ({ip_punkt[0]:.0f}, {ip_punkt[1]:.0f})")
|
||||
|
||||
plt.xlim(0, 12)
|
||||
plt.ylim(0, 10)
|
||||
plt.xlabel("Anzahl Arbitrage-Bots ($x_A$)", fontsize=11)
|
||||
plt.ylabel("Anzahl Trendfolge-Bots ($x_B$)", fontsize=11)
|
||||
plt.title("Polyeder des zulässigen Bereichs mit Niveaulinien der Zielfunktion", fontsize=13)
|
||||
plt.grid(True, linestyle=":", alpha=0.6)
|
||||
plt.legend(loc="upper right", framealpha=0.9)
|
||||
plt.tight_layout()
|
||||
ziel = os.path.join(OUTPUT_DIR, "feasible_region_2d.png")
|
||||
plt.savefig(ziel, dpi=150)
|
||||
print(f"Visualisierung gespeichert unter '{ziel}'")
|
||||
Loading…
Reference in a new issue