Files
mandelbrot/md/erklärung.md
2026-01-30 17:59:16 +01:00

685 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Die Mandelbrot-Menge: Von Null auf Visualisierung
## 1. Was sind komplexe Zahlen?
### 1.1 Die imaginäre Einheit
Normale Zahlen (reelle Zahlen) kennen wir alle: 1, 2, 3.5, -7, π usw. Diese liegen alle auf einem eindimensionalen Zahlenstrahl.
Komplexe Zahlen erweitern dieses Konzept in die zweite Dimension. Der Trick: Wir erfinden eine neue Zahl namens **i** (die imaginäre Einheit) mit der Eigenschaft:
```
i² = -1
```
Das ist zunächst verwirrend, weil keine normale Zahl diese Eigenschaft hat (sowohl 2² = 4 als auch (-2)² = 4). Aber mathematisch können wir mit **i** genauso rechnen wie mit normalen Zahlen.
### 1.2 Aufbau komplexer Zahlen
Eine komplexe Zahl besteht aus zwei Teilen:
```
z = a + bi
```
- **a** = Realteil (eine normale Zahl)
- **b** = Imaginärteil (auch eine normale Zahl)
- **i** = die imaginäre Einheit
**Beispiele:**
- `3 + 4i` → Realteil: 3, Imaginärteil: 4
- `2 - 5i` → Realteil: 2, Imaginärteil: -5
- `7 + 0i = 7` → eine rein reelle Zahl
- `0 + 3i = 3i` → eine rein imaginäre Zahl
### 1.3 Visualisierung: Die komplexe Ebene
Statt einem Zahlenstrahl verwenden wir eine Ebene:
- **x-Achse (horizontal)**: Realteil
- **y-Achse (vertikal)**: Imaginärteil
Die Zahl `3 + 4i` liegt also am Punkt (3, 4) in dieser Ebene.
```
Imaginärteil (y)
5 |
4 | • (3+4i)
3 |
2 |
1 |
0 |-------|-------|→ Realteil (x)
-1 | 2 4
-2 |
```
### 1.4 Rechnen mit komplexen Zahlen
#### Addition
Einfach: Addiere Real- und Imaginärteile getrennt.
```
(a + bi) + (c + di) = (a + c) + (b + d)i
```
**Beispiel:**
```
(3 + 4i) + (1 + 2i) = (3+1) + (4+2)i = 4 + 6i
```
#### Multiplikation
Hier wird's interessanter. Wir multiplizieren wie bei Klammern, aber beachten dass **i² = -1**:
```
(a + bi) × (c + di) = ac + adi + bci + bdi²
= ac + adi + bci + bd(-1)
= (ac - bd) + (ad + bc)i
```
**Beispiel:**
```
(3 + 4i) × (1 + 2i) = 3×1 + 3×2i + 4i×1 + 4i×2i
= 3 + 6i + 4i + 8i²
= 3 + 10i + 8×(-1)
= 3 + 10i - 8
= -5 + 10i
```
#### Spezialfall: Quadrieren
Besonders wichtig für die Mandelbrot-Menge ist das Quadrieren:
```
(a + bi)² = (a + bi) × (a + bi)
= a² + abi + abi + (bi)²
= a² + 2abi + b²i²
= a² + 2abi - b²
= (a² - b²) + 2abi
```
**Merke dir diese Formel:**
```
(a + bi)² = (a² - b²) + (2ab)i
```
**Beispiel:**
```
(3 + 4i)² = (3² - 4²) + (2×3×4)i
= (9 - 16) + 24i
= -7 + 24i
```
### 1.5 Betrag (Abstand vom Ursprung)
Der Betrag einer komplexen Zahl ist ihr Abstand vom Ursprung (0, 0). Mit dem Satz des Pythagoras:
```
|a + bi| = √(a² + b²)
```
**Beispiel:**
```
|3 + 4i| = √(3² + 4²) = √(9 + 16) = √25 = 5
```
In der Ebene visualisiert ist das die Länge des Pfeils vom Ursprung zum Punkt (3, 4).
---
## 2. Die Mandelbrot-Iteration
### 2.1 Die Grundidee
Die Mandelbrot-Menge ist eine Menge von komplexen Zahlen **c**, die eine bestimmte Eigenschaft haben.
Für jede komplexe Zahl **c** führen wir folgende Iteration durch:
```
z₀ = 0
z₁ = z₀² + c = 0² + c = c
z₂ = z₁² + c
z₃ = z₂² + c
z₄ = z₃² + c
...
```
**Die Frage:** Bleibt die Folge z₀, z₁, z₂, z₃, ... beschränkt (in der Nähe des Ursprungs), oder explodiert sie gegen unendlich?
**Definition:** Die Zahl **c** gehört zur Mandelbrot-Menge, wenn die Folge beschränkt bleibt (nicht gegen unendlich geht).
### 2.2 Beispiel 1: c = 0
```
z₀ = 0
z₁ = 0² + 0 = 0
z₂ = 0² + 0 = 0
z₃ = 0² + 0 = 0
...
```
Die Folge bleibt bei 0. **c = 0 gehört zur Mandelbrot-Menge.**
### 2.3 Beispiel 2: c = 1
```
z₀ = 0
z₁ = 0² + 1 = 1
z₂ = 1² + 1 = 2
z₃ = 2² + 1 = 5
z₄ = 5² + 1 = 26
z₅ = 26² + 1 = 677
...
```
Die Folge explodiert! **c = 1 gehört NICHT zur Mandelbrot-Menge.**
### 2.4 Beispiel 3: c = -1
```
z₀ = 0
z₁ = 0² + (-1) = -1
z₂ = (-1)² + (-1) = 1 - 1 = 0
z₃ = 0² + (-1) = -1
z₄ = (-1)² + (-1) = 0
...
```
Die Folge springt zwischen -1 und 0 hin und her. Sie bleibt beschränkt. **c = -1 gehört zur Mandelbrot-Menge.**
### 2.5 Beispiel 4: c = i (eine komplexe Zahl!)
```
z₀ = 0
z₁ = 0² + i = i
z₂ = i² + i = -1 + i
z₃ = (-1 + i)² + i
```
Berechnen wir z₃ mit unserer Quadrierformel (a=-1, b=1):
```
(-1 + i)² = ((-1)² - 1²) + (2×(-1)×1)i
= (1 - 1) + (-2)i
= -2i
z₃ = -2i + i = -i
```
Weiter:
```
z₄ = (-i)² + i = (0 + (-1)i)² + i
= (0² - (-1)²) + (2×0×(-1))i + i
= -1 + 0i + i
= -1 + i
```
Moment! z₄ = z₂. Die Folge wiederholt sich jetzt:
```
z₂ = -1 + i
z₃ = -i
z₄ = -1 + i
z₅ = -i
...
```
Die Folge bleibt beschränkt (pendelt zwischen zwei Werten). **c = i gehört zur Mandelbrot-Menge.**
---
## 3. Der Algorithmus zur Visualisierung
### 3.1 Warum nicht unendlich lange testen?
Wir können nicht unendlich viele Iterationen durchführen. Daher verwenden wir zwei Abbruchkriterien:
**Kriterium 1: Divergenz erkannt**
Mathematisch bewiesen: Wenn der Betrag von z jemals größer als 2 wird (|z| > 2), dann wird die Folge definitiv gegen unendlich gehen.
Da |z| = √(real² + imag²) ist, können wir stattdessen prüfen:
```
real² + imag² > 4
```
(Das spart die Wurzelberechnung, weil 2² = 4)
**Kriterium 2: Maximale Iterationen**
Wir setzen eine Obergrenze, z.B. 100 oder 1000 Iterationen. Wenn wir diese erreichen ohne Divergenz, nehmen wir an, dass c zur Menge gehört.
### 3.2 Der Pseudocode
Für jede komplexe Zahl c = (c_real, c_imag):
```
z_real = 0
z_imag = 0
iteration = 0
max_iterations = 1000
solange (iteration < max_iterations):
// Berechne z² + c
z_real_neu = z_real² - z_imag² + c_real
z_imag_neu = 2 × z_real × z_imag + c_imag
z_real = z_real_neu
z_imag = z_imag_neu
// Divergenz-Check
wenn (z_real² + z_imag² > 4):
break // Divergenz erkannt!
iteration = iteration + 1
// Ergebnis: iteration
// - Wenn iteration = max_iterations: c ist in der Menge (schwarz färben)
// - Sonst: c ist außerhalb (Farbe basierend auf iteration)
```
### 3.3 Wichtige Details zur Implementierung
**1. Reihenfolge der Berechnung**
```cpp
// FALSCH:
z_real = z_real * z_real - z_imag * z_imag + c_real;
z_imag = 2 * z_real * z_imag + c_imag; // Nutzt bereits das neue z_real!
```
```cpp
// RICHTIG:
double z_real_neu = z_real * z_real - z_imag * z_imag + c_real;
double z_imag_neu = 2 * z_real * z_imag + c_imag;
z_real = z_real_neu;
z_imag = z_imag_neu;
```
**2. Optimierung der Divergenz-Prüfung**
```cpp
// Berechne die Quadrate nur einmal:
double z_real_squared = z_real * z_real;
double z_imag_squared = z_imag * z_imag;
double z_real_neu = z_real_squared - z_imag_squared + c_real;
double z_imag_neu = 2 * z_real * z_imag + c_imag;
// Divergenz-Check
if (z_real_squared + z_imag_squared > 4.0) {
break;
}
```
---
## 4. Von Pixeln zur komplexen Ebene
### 4.1 Das Problem
Dein Bildschirm hat Pixel mit Koordinaten:
- x: 0 bis fenster_breite (z.B. 0 bis 800)
- y: 0 bis fenster_hoehe (z.B. 0 bis 600)
Die interessante Region der Mandelbrot-Menge liegt bei:
- Real-Achse: ungefähr -2.5 bis +1.0
- Imaginär-Achse: ungefähr -1.0 bis +1.0
Wir müssen jeden Pixel auf einen Punkt in der komplexen Ebene abbilden.
### 4.2 Die Mapping-Formel
#### Schritt 1: Normalisiere Pixelkoordinaten auf [0, 1]
```
x_normiert = x / fenster_breite // Wert zwischen 0 und 1
y_normiert = y / fenster_hoehe // Wert zwischen 0 und 1
```
#### Schritt 2: Skaliere auf den gewünschten Bereich
```
c_real = min_real + x_normiert × (max_real - min_real)
c_imag = min_imag + y_normiert × (max_imag - min_imag)
```
#### Beispiel mit konkreten Zahlen
Angenommen:
- Fenster: 800×600 Pixel
- Bereich: real [-2.5, 1.0], imaginär [-1.0, 1.0]
Für Pixel (400, 300) (die Fenstermitte):
```
x_normiert = 400 / 800 = 0.5
y_normiert = 300 / 600 = 0.5
c_real = -2.5 + 0.5 × (1.0 - (-2.5))
= -2.5 + 0.5 × 3.5
= -2.5 + 1.75
= -0.75
c_imag = -1.0 + 0.5 × (1.0 - (-1.0))
= -1.0 + 0.5 × 2.0
= -1.0 + 1.0
= 0.0
```
Die Fenstermitte entspricht also der komplexen Zahl c = -0.75 + 0i.
### 4.3 Koordinatensysteme beachten
**Problem:** In den meisten Grafiksystemen ist y=0 oben und y wächst nach unten. In der Mathematik wächst die imaginäre Achse nach oben.
**Lösung 1:** Y-Koordinate umdrehen
```
y_normiert = 1.0 - (y / fenster_hoehe)
```
**Lösung 2:** min_imag und max_imag vertauschen
```
c_imag = max_imag - y_normiert × (max_imag - min_imag)
```
Beide führen zum gleichen Ergebnis: Die Mandelbrot-Menge wird richtig herum angezeigt.
### 4.4 Seitenverhältnis (Aspect Ratio)
Wenn dein Fenster nicht quadratisch ist, musst du aufpassen, dass Kreise nicht zu Ellipsen werden.
**Option 1:** Passe den Bereich an das Fensterverhältnis an
```
aspect_ratio = fenster_breite / fenster_hoehe
real_span = max_real - min_real
imag_span = max_imag - min_imag
// Zentriere und passe an
center_real = (min_real + max_real) / 2
center_imag = (min_imag + max_imag) / 2
if (aspect_ratio > 1.0) {
// Fenster ist breiter als hoch → erweitere Real-Achse
half_span = imag_span * aspect_ratio / 2
min_real = center_real - half_span
max_real = center_real + half_span
} else {
// Fenster ist höher als breit → erweitere Imaginär-Achse
half_span = real_span / aspect_ratio / 2
min_imag = center_imag - half_span
max_imag = center_imag + half_span
}
```
**Option 2:** Rendere in ein quadratisches Bild und zeige es zentriert im Fenster
---
## 5. Färbung und Visualisierung
### 5.1 Basis-Färbung
Das Einfachste:
- **In der Menge** (max_iterations erreicht): Schwarz
- **Außerhalb**: Färbung basierend auf der Iterationszahl
```
if (iteration == max_iterations) {
farbe = SCHWARZ
} else {
// iteration geht von 0 bis max_iterations-1
helligkeitswert = iteration / max_iterations
farbe = helligkeitswert // z.B. 0 = schwarz, 1 = weiß
}
```
### 5.2 Farb-Paletten
Statt Graustufen kannst du Farbverläufe verwenden:
**Linear mapping:**
```
farbindex = (iteration × 256) / max_iterations
farbe = farbpalette[farbindex]
```
**Beliebte Paletten:**
- Regenbogen (HSV mit variierendem Hue)
- Blau-Violett-Verlauf
- Feuer (Schwarz → Rot → Orange → Gelb → Weiß)
### 5.3 Smooth Coloring (kontinuierliche Färbung)
Problem: Mit der einfachen Methode sieht man harte "Bänder" zwischen den Iterationsstufen.
Lösung: Nutze den finalen Betragswert für eine feinere Färbung:
```
wenn divergiert:
// Wie stark über der Grenze sind wir?
betrag_quadrat = z_real² + z_imag²
// Smooth iteration count
smooth_iteration = iteration + 1 - log(log(betrag_quadrat) / log(2)) / log(2)
// Nutze smooth_iteration für Färbung
```
Das ergibt sanfte Farbverläufe ohne Bänder. (Dies ist mathematisch etwas fortgeschritten, aber das Ergebnis sieht viel besser aus.)
---
## 6. Zoom und Navigation
### 6.1 Zoom implementieren
Beim Zoomen verkleinerst du den sichtbaren Bereich der komplexen Ebene.
**Zoom um einen Faktor:**
```
zoom_factor = 2.0 // 2x näher heran
center_real = (min_real + max_real) / 2
center_imag = (min_imag + max_imag) / 2
span_real = (max_real - min_real) / zoom_factor
span_imag = (max_imag - min_imag) / zoom_factor
min_real = center_real - span_real / 2
max_real = center_real + span_real / 2
min_imag = center_imag - span_imag / 2
max_imag = center_imag + span_imag / 2
```
### 6.2 Zoom auf Mausposition
Wenn der User auf Pixel (mouse_x, mouse_y) klickt:
```
// Berechne die komplexe Zahl an der Mausposition
mouse_real = min_real + (mouse_x / fenster_breite) × (max_real - min_real)
mouse_imag = min_imag + (mouse_y / fenster_hoehe) × (max_imag - min_imag)
// Zentriere auf diese Position und zoome
span_real = (max_real - min_real) / zoom_factor
span_imag = (max_imag - min_imag) / zoom_factor
min_real = mouse_real - span_real / 2
max_real = mouse_real + span_real / 2
min_imag = mouse_imag - span_imag / 2
max_imag = mouse_imag + span_imag / 2
```
### 6.3 Interaktive Navigation
**Pan (Verschieben):**
```
verschiebung_real = delta_x / fenster_breite × (max_real - min_real)
verschiebung_imag = delta_y / fenster_hoehe × (max_imag - min_imag)
min_real -= verschiebung_real
max_real -= verschiebung_real
min_imag -= verschiebung_imag
max_imag -= verschiebung_imag
```
---
## 7. Performance-Optimierungen
### 7.1 Frühe Abbrüche
**Periodizitätsprüfung:** Speichere z in regelmäßigen Abständen. Wenn z sich wiederholt, bist du in einem Zyklus → in der Menge.
**Hauptkardioiden-Test:** Der große "Körper" der Mandelbrot-Menge (eine Kardioidform) kann analytisch getestet werden:
```
// Teste ob c in der Hauptkardioide liegt
q = (c_real - 0.25)² + c_imag²
if (q × (q + (c_real - 0.25)) < 0.25 × c_imag²) {
// In der Hauptkardioide → direkt als "in der Menge" werten
return max_iterations
}
// Teste ob c im Periode-2-Bulb liegt
if ((c_real + 1)² + c_imag² < 0.0625) {
// Im Bulb → in der Menge
return max_iterations
}
```
### 7.2 Symmetrie nutzen
Die Mandelbrot-Menge ist symmetrisch zur reellen Achse:
- Wenn (a + bi) in der Menge ist, dann auch (a - bi)
Du kannst nur die obere Hälfte berechnen und spiegeln.
### 7.3 Multi-Threading
Jeder Pixel kann unabhängig berechnet werden → perfekt für Parallelisierung!
Teile das Bild in Streifen oder Kacheln auf und berechne diese in verschiedenen Threads.
---
## 8. Interessante Koordinaten zum Erkunden
### Klassische Ansicht (Gesamtüberblick)
```
Real: [-2.5, 1.0]
Imag: [-1.25, 1.25]
```
### Seepferdchen-Tal
```
Real: [-0.75, -0.735]
Imag: [0.1, 0.115]
max_iterations: 500+
```
### Spiralen
```
Real: [-0.7, -0.65]
Imag: [0.4, 0.45]
max_iterations: 1000+
```
### Mini-Mandelbrot
```
Real: [-0.16, -0.14]
Imag: [1.025, 1.045]
max_iterations: 2000+
```
**Tipp:** Je tiefer du zoomst, desto höher sollte max_iterations sein, um Details zu sehen!
---
## 9. Häufige Fehler und deren Lösung
### 9.1 "Ich sehe nur Schwarz oder nur Weiß"
**Ursachen:**
- max_iterations zu niedrig oder zu hoch
- Koordinatenbereich außerhalb der interessanten Region
- Mapping-Formel falsch
**Lösung:** Beginne mit der klassischen Ansicht und max_iterations = 100.
### 9.2 "Das Bild ist verzerrt"
**Ursache:** Seitenverhältnis nicht korrekt berücksichtigt.
**Lösung:** Siehe Abschnitt 4.4 zur Aspect Ratio.
### 9.3 "Die Iteration zählt falsch"
**Ursache:** Du verwendest das neue z_real bei der Berechnung von z_imag_neu.
**Lösung:** Speichere beide neue Werte in temporären Variablen.
### 9.4 "Beim Zoomen wird das Bild unscharf"
**Ursache:** Bei tiefen Zoom-Stufen reicht die Präzision von `double` nicht mehr aus.
**Lösung:**
- Nutze höhere Präzision (z.B. `long double`)
- Für extreme Zooms: arbitrary-precision Bibliotheken (GMP, MPFR)
### 9.5 "Die Berechnung ist sehr langsam"
**Lösungen:**
- Reduziere max_iterations für Vorschau-Rendering
- Implementiere Multi-Threading
- Nutze Periodizitätsprüfung und analytische Tests
- Rendere in niedrigerer Auflösung und skaliere hoch
---
## 10. Zusammenfassung: Die komplette Visualisierungspipeline
```
FÜR JEDEN PIXEL (x, y) IM FENSTER:
1. MAPPING: Konvertiere Pixel zu komplexer Zahl
c_real = min_real + (x / breite) × (max_real - min_real)
c_imag = min_imag + (y / höhe) × (max_imag - min_imag)
2. ITERATION: Teste ob c in der Menge liegt
z_real = 0
z_imag = 0
iteration = 0
SOLANGE iteration < max_iterations:
// Berechne z² + c
temp_real = z_real² - z_imag² + c_real
temp_imag = 2 × z_real × z_imag + c_imag
z_real = temp_real
z_imag = temp_imag
// Divergenz?
WENN z_real² + z_imag² > 4:
BREAK
iteration++
3. FÄRBUNG: Basierend auf Iterationszahl
WENN iteration == max_iterations:
farbe = SCHWARZ (in der Menge)
SONST:
farbe = berechne_farbe(iteration)
4. ZEICHNE: Setze Pixel (x, y) auf farbe
```
---
## Viel Erfolg!
Die Mandelbrot-Menge ist ein faszinierendes mathematisches Objekt. Beim Hineinzoomen entdeckst du immer neue Strukturen Spiralen, Seepferdchen-Täler, Mini-Kopien der gesamten Menge.
Ein paar abschließende Tipps:
- Beginne mit niedrigen max_iterations (50-100) für schnelle Tests
- Implementiere Zoom und Pan für interaktive Exploration
- Experimentiere mit verschiedenen Farbpaletten
- Speichere interessante Koordinaten, die du findest
Die Selbstähnlichkeit der Mandelbrot-Menge ist unendlich du kannst beliebig tief zoomen und findest immer neue Details (bis zur Grenze der Gleitkomma-Präzision).