kleine Änderungen

This commit is contained in:
dschlueter 2026-09-09 07:37:07 +02:00
commit dc088fa214
47 changed files with 183868 additions and 573 deletions

View file

@ -0,0 +1,195 @@
#!/usr/bin/env python3
# erzeuge_3d_konvexitaet.py
"""
Erzeugt die Landschaftsgrafik zum Abschnitt Konvexitaet:
bilder_04/kap02_konvexitaet_landschaft.svg
ERGAENZT das vorhandene kap02_konvexitaet.svg, ersetzt es nicht: Jenes zeigt die
Sehnenbedingung - die Definition. Dieses zeigt ihre Folge - dass bei einer
nicht-konvexen Funktion der Startpunkt darueber entscheidet, was am Ende
herauskommt.
Beide Tafeln zeigen DIESELBE Funktion wie das Kapitelprogramm
Konvexitaet_Demo.py:
links f(x) = x^2 + 3 sin(3x) - eindimensional
rechts f(x1, x2) = x1^2 + 3 sin(3 x1) + x2^2 + 3 sin(3 x2) - separabel
Die rechte Funktion ist die separable Fortsetzung der linken: Auf jeder Achse
liegt exakt die Kurve der linken Tafel. Deshalb ist die rechte Tafel keine neue
Behauptung, sondern dieselbe eine Dimension weiter - und die Taeler, die links
nebeneinander liegen, werden rechts zu einem Gitter aus Mulden.
Gerechnet wird mit BFGS aus denselben Startpunkten wie im Buchprogramm. Die
fuenf Ergebnisse der linken Tafel muessen deshalb Ziffer fuer Ziffer der im
Kapitel abgedruckten Tabelle entsprechen; pruefe_gegen_buch() bricht ab, wenn
nicht. Ein Bild, das andere Minima zeigt als die Tabelle daneben, waere
schlimmer als gar kein Bild.
Aufruf (aus dem Repository-Wurzelverzeichnis):
python3 bilder_04/erzeuge_3d_konvexitaet.py
Benoetigt: numpy, scipy, matplotlib
"""
from __future__ import annotations
import os
import sys
import numpy as np
from scipy.optimize import minimize
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from stil_04 import FARBEN, speichere # noqa: E402
import matplotlib.pyplot as plt # noqa: E402
# Die Startpunkte des Buchprogramms - nicht selbst gewaehlt.
STARTPUNKTE = [-3.0, -1.5, 0.0, 1.5, 3.0]
# Die im Kapitel abgedruckte Tabelle: Start -> (Minimum, Funktionswert).
# Nur zum Gegenpruefen; gerechnet wird unabhaengig davon.
TABELLE_IM_BUCH = {
-3.0: (-2.4280, 3.3695),
-1.5: (-0.4874, -2.7448),
0.0: (-0.4874, -2.7448),
1.5: (1.4606, -0.7042),
3.0: (3.3817, 9.4570),
}
def welle(x):
"""Die Funktion des Buchprogramms: Parabel mit aufmodulierter Welle."""
return x ** 2 + 3.0 * np.sin(3.0 * x)
def welle_1d(x):
"""Dieselbe Funktion in der Form, die scipy.optimize erwartet."""
return welle(x[0])
def welle_2d(x):
"""Separable Fortsetzung in zwei Dimensionen."""
return welle(x[0]) + welle(x[1])
def suche_1d():
"""Lokale Suche von jedem Startpunkt - wie in Teil 2 des Buchprogramms."""
ergebnisse = []
for start in STARTPUNKTE:
res = minimize(welle_1d, x0=[start], method="BFGS")
ergebnisse.append((start, float(res.x[0]), float(res.fun)))
return ergebnisse
def suche_2d(start):
"""Dieselbe lokale Suche in 2D, mit aufgezeichnetem Weg."""
weg = [np.array(start, dtype=float)]
minimize(welle_2d, x0=list(start), method="BFGS",
callback=lambda xk: weg.append(np.array(xk, dtype=float)))
return np.array(weg)
def pruefe_gegen_buch(ergebnisse) -> None:
"""Bild und abgedruckte Tabelle muessen dieselben Minima nennen."""
for start, ort, wert in ergebnisse:
soll_ort, soll_wert = TABELLE_IM_BUCH[start]
if abs(ort - soll_ort) > 5e-4 or abs(wert - soll_wert) > 5e-4:
raise SystemExit(
f"Konvexitaets-Landschaft: Start {start:+.1f} fuehrt zu "
f"x = {ort:.4f} (f = {wert:.4f}), das Kapitel druckt "
f"x = {soll_ort:.4f} (f = {soll_wert:.4f}) ab.")
gefunden = len({round(ort, 3) for _, ort, _ in ergebnisse})
if gefunden != 4:
raise SystemExit(
f"Konvexitaets-Landschaft: {gefunden} verschiedene Minima, das "
f"Kapitel druckt 4 ab.")
def zeichne(ergebnisse, wege) -> None:
figur, achsen = plt.subplots(1, 2, figsize=(10.0, 4.3))
# --- links: die Funktion des Buchprogramms mit ihren fuenf Laeufen -----
links = achsen[0]
x = np.linspace(-3.8, 3.8, 800)
links.plot(x, welle(x), "-", color=FARBEN["haupt"], linewidth=2.0,
zorder=3)
bester = min(ergebnisse, key=lambda z: z[2])
for start, ort, wert in ergebnisse:
global_ = abs(ort - bester[1]) < 1e-3
farbe = FARBEN["gut"] if global_ else FARBEN["fehler"]
links.annotate("", xy=(ort, wert), xytext=(start, welle(start)),
arrowprops=dict(arrowstyle="->", color=farbe,
linewidth=1.4,
connectionstyle="arc3,rad=0.18"),
zorder=4)
links.plot(start, welle(start), "o", color=farbe, markersize=6,
zorder=5)
links.plot(ort, wert, "*", color=farbe, markersize=13, zorder=6)
links.set_xlabel("$x$")
links.set_ylabel("$f(x)$")
links.set_title("fünf Startpunkte, vier verschiedene Minima",
fontsize=10.5, color=FARBEN["text"])
links.annotate("grün: im tiefsten Tal gelandet\nrot: in einem anderen",
xy=(0.36, 0.97), xycoords="axes fraction", fontsize=8.5,
color=FARBEN["text"], va="top")
# --- rechts: dieselbe Funktion, eine Dimension weiter ------------------
rechts = achsen[1]
gitter = np.linspace(-3.8, 3.8, 420)
xx, yy = np.meshgrid(gitter, gitter)
zz = welle(xx) + welle(yy)
rechts.contourf(xx, yy, zz, levels=28, cmap="Blues_r", alpha=0.7, zorder=1)
rechts.contour(xx, yy, zz, levels=16, colors=[FARBEN["linie"]],
linewidths=0.5, zorder=2)
for name, weg, farbe, versatz, ausrichtung in wege:
rechts.plot(weg[:, 0], weg[:, 1], "-", color=farbe, linewidth=2.2,
zorder=4)
rechts.plot(*weg[0], "o", color=farbe, markersize=7, zorder=5)
rechts.plot(*weg[-1], "*", color=farbe, markersize=15, zorder=6)
rechts.annotate(f"{name}\nf = {welle_2d(weg[-1]):.2f}",
xy=weg[-1], xytext=versatz,
textcoords="offset points", fontsize=8.5,
color=farbe, fontweight="bold", ha=ausrichtung,
zorder=7)
rechts.set_xlabel("$x_1$")
rechts.set_ylabel("$x_2$")
rechts.set_title("dieselbe Funktion in zwei Dimensionen",
fontsize=10.5, color=FARBEN["text"])
rechts.grid(visible=False)
speichere(figur, "kap02_konvexitaet_landschaft")
if __name__ == "__main__":
ergebnisse = suche_1d()
for start, ort, wert in ergebnisse:
print(f" Start {start:>5.1f} -> x = {ort:>8.4f} f = {wert:>8.4f}")
pruefe_gegen_buch(ergebnisse)
print(" stimmt mit der abgedruckten Tabelle überein (5 Zeilen, 4 Minima)")
weg_a = suche_2d((-1.4, -1.6))
weg_b = suche_2d((2.9, 3.1))
if welle_2d(weg_a[-1]) >= welle_2d(weg_b[-1]):
raise SystemExit("Konvexitaets-Landschaft: Lauf A landet nicht "
"tiefer als Lauf B - Farben und Aussage verkehrt.")
if np.linalg.norm(weg_a[-1] - weg_b[-1]) < 1.0:
raise SystemExit("Konvexitaets-Landschaft: Beide 2D-Laeufe enden im "
"selben Tal - dann ist keine Falle zu sehen.")
# Der Begleittext nennt diesen Abstand ("mehr als 24 Einheiten").
abstand = welle_2d(weg_b[-1]) - welle_2d(weg_a[-1])
if not 24.0 <= abstand < 25.0:
raise SystemExit(
f"Konvexitaets-Landschaft: Der Abstand der beiden Laeufe betraegt "
f"{abstand:.2f} Einheiten. Der Text im Kapitel sagt 'mehr als 24' "
f"- beides muss zusammenpassen.")
print(f" 2D: Lauf A endet bei f = {welle_2d(weg_a[-1]):.2f}, "
f"Lauf B bei f = {welle_2d(weg_b[-1]):.2f}")
# Versatz und Ausrichtung der Beschriftung: Lauf B endet in der oberen
# rechten Ecke, dort muss der Text nach innen zeigen.
zeichne(ergebnisse,
[("Start A", weg_a, FARBEN["gut"], (16, 16), "left"),
("Start B", weg_b, FARBEN["fehler"], (-14, -30), "right")])