kleine Änderungen
This commit is contained in:
parent
0f9c815b86
commit
dc088fa214
47 changed files with 183868 additions and 573 deletions
195
bilder_04/erzeuge_3d_konvexitaet.py
Normal file
195
bilder_04/erzeuge_3d_konvexitaet.py
Normal 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")])
|
||||
Loading…
Reference in a new issue