Malen nach Zahlen

Die Mathematik hinter dem »Apfelmännchen«

Die Mandelbrot-Menge

Jemand, der nichts von möglichen mathematischen Hintergründen der Mandelbrot-Menge wüsste, könnte beim Betrachten dieser kunstvollen Fraktalgrafik-Bilder mit ihren komplexen und vielfältigen Strukturen glatt auf die Idee kommen zu fragen, welcher begnadete Künstler da wohl am Werk gewesen sein mag... ;-) Am wenigsten würde er wohl vermuten, dass dahinter lediglich eine äußerst schlicht aussehende mathematische Gleichung steckt, nämlich:

 Zn+1 = Zn² + C  

Was hat es damit auf sich?

Benannt wurde die Mandelbrot-Menge nach dem Mathematiker Benoît Mandelbrot (1924–2010), der dieses Fraktal bei der Untersuchung von Julia-Mengen entdeckt und beschrieben hat. Der Definition nach besteht die Mandelbrot-Menge aus der Menge aller komplexen Zahlen C, für die die Iterationsvorschrift Z->Z²+C mit Z=0 nicht nach Unendlich strebt. Hmmm. Und was heißt das jetzt genau?

Komplexe Zahlen

Zunächst zum Begriff "komplexe Zahlen": Dabei handelt es sich um "zweidimensionale" Zahlen, die aus einem Realteil und einem Imaginärteil bestehen. Beispielsweise kann man die komplexe Zahl Z auch als a + bi schreiben, wobei a den Realteil und b den Imaginärteil der komplexen Zahl Z angibt. Der Zusatz i ist dabei die sogenannte "imaginäre Einheit". Imaginär deshalb, weil diese nur in der Vorstellung (der Mathematiker) existiert. Diese brauchten nämlich eine Zahl, deren Quadrat -1 ergibt, und da es in der Menge der "normalen" (reellen) Zahlen keine solche Zahl gibt, erfanden sie einfach die "imaginäre Einheit" i und definierten i² = -1 bzw. i=√(-1). Diese Schlawiner... ;-)

Wegen ihrer zwei Anteile (Real- und Imaginärteil) lassen sich die komplexen Zahlen in einem (x/y)-Koordinatensystem, der sogenannten Gaußschen Zahlenebene (auch Komplexe Ebene genannt), darstellen. Das sieht dann z.B. für die beiden komplexen Zahlen 4+3i und 3-2i so aus:



Die beiden komplexen Zahlen 4+3i und 3-2i in der Gaußschen Zahlenebene
 

Mit komplexen Zahlen kann man auch rechnen, diese also addieren, subtrahieren, multiplizieren, dividieren usw. Hier nur in Kurzform die vier "Grundrechenarten" für komplexe Zahlen am Beispiel der Zahlen Z1=a+bi und Z2=c+di:

Mehr zu komplexen Zahlen und wie man mit ihnen rechnet, findet man z.B. bei frustfrei-lernen.de oder Wikipedia. Einen Online-Rechner für komplexe Zahlen gibt es bei Calc 3D.

Iterationen

So, nun zu der eingangs erwähnten Iterationsvorschrift Z->Z²+C. Iterationen sind wiederholte Anwendungen einer Rechenoperation. Dabei wird das Ergebnis der vorherigen Rechenoperation in die nachfolgende Rechenoperation eingesetzt, und zwar immer wieder, bei jeder Iteration erneut.



 

Dadurch ergibt sich quasi eine Rückkopplungsschleife – so ähnlich wie bei einer Videokamera, die auf einen Bildschirm gerichtet ist, der das Bild anzeigt, das die Kamera aufnimmt. Die Kamera filmt also also ihr eigenes Bild, allerdings nicht 1:1, sondern leicht verzerrt und gestört. Wer das schon einmal ausprobiert hat, wird festgestellt haben, dass hierduch interessante, chaotisch bewegte Muster entstehen können. So ähnlich kann man sich das auch bei einer Mandelbrot-Rückkopplung vorstellen: Z ist sozusagen das Videosignal, das aus der Kamera kommt. Dieses wird an den Bildschirm weitergeleitet, dabei elektronisch verarbeitet und aufbreitet und letztlich als Bild angezeigt und wieder von der Kamera aufgenommen. Die dabei unvermeidlich entstehenden Veränderungen des Videosignals Z entsprechen hierbei der Rechenoperation Z²+C. Im Unterschied zu dem Beispiel der Kamera-Bildschirm-Rückkopplung, wo es tatsächliche zufällige Störeinflüsse gibt, handelt es sich bei der Mandelbrot-Rückkopplung allerdings um einen vollständig deterministischen Vorgang, d.h. das Ergebnis ist "festgelegt" in dem Sinne, dass gleiche Eingangsparameter immer dieselben Ergebnisse liefern werden (logisch; Mathematik kennt keine Zufälle).

Unsere Leinwand

Bei der Mandelbrot-Rückkopplung wird die Iterationsvorschrift Z->Z²+C für jeden einzelnen Bildpunkt angewendet. Der gewählte Bildausschnitt wird dazu einfach über die Gaußsche Zahlenebene gelegt:



Der darzustellende Bildausschnitt (grün) über der Gaußschen Zahlenebene
 

In obigem Beispiel wurde ein Bildausschnitt von 600 × 450 Pixeln mittig über die Gaußsche Zahlenebene mit der Ausdehnung von Real -3 ... 3 und Imaginär -2,25 ... 2,25 gelegt. Jeder Bildpunkt entspricht somit einer komplexen Zahl C auf der Gaußschen Zahlenebene. Beispielsweise entspricht in obiger Abbildung der rot markierte Bildpunkt bei x=500 und y=75 der komplexen Zahl C = 2+1,5i.

Zahlenwust

Für jeden einzelnen Bildpunkt unseres Bildausschnittes von ganz links oben (0/0) bis nach ganz rechts unten (599/449) wird nun ermittelt, ob die dazugehörige Zahl C zur Mandelbrot-Menge gehört oder nicht. Hierzu wird die erwähnte Iterationsvorschrift Z->Z²+C für jeden Bildpunkt so oft angewandt, bis entweder das Ergebnis Z aus einem gedachten Kreis mit Radius 2 um den Nullpunkt der Gaußschen Zahlenebene herum "ausbricht", oder bis eine bestimmte Anzahl von Iterationen (z.B. 200) erreicht ist. Der Startwert von Z ist dabei für jeden Bildpunkt immer 0. Die Zahl Z wird quadriert und C hinzuaddiert. Das Ergebnis wird wieder quadriert und dasselbe C hinzuaddiert. Und so weiter, und so fort.

Hier einmal zwei Beispiele für die beiden komplexen Zahlen C = 0,25+0,5i und C = 0,5-0,5i. Dargestellt sind jeweils die Anzahl der Iterationen (it), der Realteil (a) und der Imaginärteil (b) der komplexen Zahl Z, dem Ergebnis der Iterationsvorschrift Z->Z²+C, sowie |Z|, dem Betrag der Zahl Z, was dem Abstand zum Ursprung 0/0 der Gaußschen Zahlenebene entspricht und sich nach dem Satz des Pythagroas aus der Wurzel aus a²+b² errechnet:
 

Beispiel 1
C = 0,25+0,5i
itab|Z|
10,250,50,559016994
20,06250,750,752599661
3-0,308593750,593750,669155561
4-0,007308960,1335449220,133744783
50,2322191750,4980478510,54952471
60,0558740830,7313125220,733443875
7-0,2816960910,5817228330,646339031
8-0,0090487670,1722619030,172499401
90,2204077170,4968824840,543573146
100,0516873580,7190334680,720888834
11-0,2643375450,5743298810,63224137
12-0,0099804750,1963660980,196619568
130,2115399650,4960803460,539300349
140,0486534470,7098816380,711546976
............
     
Beispiel 2
C = 0,5-0,5i
itab|Z|
10,5-0,50,707106781
20,5-11,118033989
3-0,25-1,51,520690633
4-1,68750,251,705918008
53,28515625-1,343753,549354258
69,486587524-9,32885742213,30499619
73,467762061-177,4980449177,5319163
8-31493,03056-1231,54197231517,10122
9990294278,777569977,4993327669,9
109,74666E+171,53634E+179,867E+17
119,2637E+352,99484E+359,73577E+35
127,6847E+715,54866E+719,47851E+71
132,8267E+1438,528E+1438,9842E+143
14-6,4736E+2874,8212E+287(zu groß)
............


Wie man bei genauem Hinsehen bereits erkennt, bleiben die Ergebnisse bei Beispiel 1 weitestgehend im Rahmen, während bei Beispiel 2 die Werte sehr schnell ansteigen und zum Schluss so groß werden, dass die Zahlen von Excel (das ich für die Berechnung benutzt habe) gar nicht mehr darstellbar sind. Der rot markierte Wert von |Z| im Beispiel 2 kennzeichnet dabei die Überschreitung des kritischen Grenzwertes von 2, also des "Fluchtradius".

Malen nach Zahlen

Da Bilder bekanntlich mehr sagen als tausend Worte (und weil Excel über so eine schöne Funktion zum Erstellen von Diagrammen verfügt), habe ich die grauen Zahlenkolonnen der obigen beiden Beispiele einmal in ein zweidimensionales Diagramm verwandelt, das die Gaußsche Zahlenebene darstellt. Und um den "Weg" der Zahl Z nach jeder Iteration besser nachverfolgen zu können, wurden zwischen den einzelnen Positionen der Zahl Z rote Linien gezeichnet:


(Bilder anklicken zum Vergrößern:)

Links: Beispiel 1 (C = 0,25+0,5i): Der Punkt Z bleibt auch nach 200 Iterationen noch innerhalb des Kreises mit Radius 2 (Konvergenz).
Mittte: Vergrößerte Darstellung des linken Bildes.
Rechts: Beispiel 2 (C = 0,5-0,5i): Der Punkt Z bricht schon nach 5 Iterationen aus dem Kreis mit Radius 2 aus (Divergenz).
 

Wie man nun schön sieht, verändert sich Z bei jeder Iteration, d.h. es "hüpft" auf der Gaußschen Zahlenebene umher, bis es entweder aus dem Kreis "abhaut" oder eben nicht. Man könnte die Mandelbrot-Menge daher bildlich gesprochen auch als die Menge aller "Gefangenen" bezeichnen, denen die Flucht aus dem Kreis nicht gelingt... ;-)

Zu Beispiel 1: Das linke und das mittlere Bild oben zeigt das Verhalten der Zahl Z mit C=0,25+0,5i als Startpunkt. Wie man sieht, springt der Punkt sozusagen "im Viereck" (das mit jeder Runde immer kleiner wird), und nähert sich dabei dem Zentrum dieses Vierecks. Auf jeden Fall bleibt er innerhalb des Kreises mit Radius 2. Man spricht in diesem Fall von Konvergenz (Zusammenlaufen). Damit gehört der Startpunkt C zur Mandelbrot-Menge.

Zu Beispiel 2: Das rechte der oberen Bilder zeigt das Verhalten der Zahl Z mit C=0,5-0,5i als Startpunkt. Hier sieht man sofort, dass der Punkt Z schon nach 5 Iterationen aus dem Kreis mit Radius 2 herausspringt (dies entspricht dem rot markierten Wert von |Z| in der obigen Zahlenfolge). Die Werte von Real- und Imaginärteil von Z werden mit jeder Iteration immer größer und nähern sich "Unendlich"; man spricht in diesem Fall von Divergenz (Auseinanderlaufen). Damit gehört der Startpunkt C nicht zur Mandelbrot-Menge.

Mit dem folgenden JavaScript-Programm kann man das "Sprungverhalten" des Punktes Z auf der Gaußschen Zahlenebene für beliebige Startpunkte C einmal "live" ausprobieren – dazu einfach mit der Maus auf die Gaußche Zahlenebene klicken oder die Maus bei gedrückt gehaltener Maustaste über der Gaußchen Zahlenebene bewegen:



[Script in einem neunen Tab/Fenster öffnen]
 

Je näher sich der Startpunkt C am Rand der türkis markierten Mandelbrot-Menge befindet, umso "chaotischer" ist das Sprungverhalten der daraus errechneten Punkte Z.

Wird nun für jeden Punkt C, der zur Mandelbrot-Menge gehört, ein farbiger Punkt gezeichnet, dann ergibt sich als Gesamtbild aller farbigen Punkte "auf wundersame Weise" die bekannte Form des »Apfelmännchens«:



Zwei Startpunkte C (0,25+0,5i und 0,5-0,5i) in der Gaußschen Zahlenebene
 

Die "Höhenlinien" oder einen Farbverlauf um das »Apfelmännchen« herum erhält man, indem man für diejenigen Zahlen C, deren Ergebnis Z sich nach maximal 200 Iterationen nicht mehr im Kreis befindet, statt Weiß eine Graustufe oder eine Farbe verwendet, die der Anzahl von Iterationen entspricht, die erforderlich waren, bis sich Z aus dem Kreis verabschiedet hat. Der farblichen Ausgestaltung sind hier keine Grenzen gesetzt.

Anmerkung: Wenn hier immer von "200 Iterationen" die Rede ist, dann ist das natürlich nur ein Beispiel. Wieviele Iterationen maximal durchlaufen werden sollen, um zu sicher zu sein, dass ein Punkt C zur Mandelbrot-Menge gehört oder nicht, ist eine willkürliche Festlegung. Je mehr Iterationen dazu benutzt werden, umso genauer wird die Mandelbrot-Menge eingegrenzt und umso mehr feine Details werden sichtbar. Ebenfalls kann man den "Fluchtradius", also den Radius des Kreises, der die Grenze zwischen Divergenz und Konvergenz festlegt, über den Minimalwert von 2 hinaus beliebig vergrößern. Je größer man diesen Radius macht, umso weniger "wellig" verlaufen die "Höhenlinien" um die Mandelbrot-Menge herum, bzw. umso harmonischer wirkt der Farbverlauf (sofern man die Punkte außerhalb der Mandelbrotmenge farblich kennzeichnet).

Das eigentlich "Chaotische" an dem Ganzen ist, dass es – vor allem am Rand der Mandelbrot-Menge – absolut nicht vorhersagbar ist, ob ein Punkt C zur Mandelbrot-Menge gehört oder nicht, also ob sich das Ergebnis Z der Iterationsvorschrift Z->Z²+C nach z.B. 200 Iterationen noch innerhalb des Kreises mit Radius 2 befindet oder nicht, und wenn nicht, wieviele Iterationen nötig sind, bis dies feststeht. Und zwar gilt das auch, wenn die Punkte noch so nahe beieinanderliegen, d.h. man kann den Ausschnitt innerhalb der Gaußschen Zahlenebene noch so klein wählen, man wird immer auf unvorhersagbares, chaotisches Verhalten treffen. Dennoch ist das Verhalten nicht rein "zufällig" (wäre das so, würde man nur "Bildrauschen" sehen), sondern trotzdem geordnet – quasi "Ordnung im Chaos" – und genau dadurch entstehen die typischen Strukturen innerhalb des Randbereiches der Mandelbrot-Menge.

Eine bisher wohl noch nicht ganz geklärte Frage ist allerdings, warum genau diese Strukturen entstehen. Wieso ausgerechnet diese "Spiralen", "Wirbel", "Seepferdchen" etc., und das in endlosen Variationen?

Programmierung

Ein Algorithmus zur Mandelbrot-Rückkopplung, der komplexe Zahlen verwendet, sieht also z.B. so aus:


function Iterate(C)
{
	Z = 0;         // Startwert für Z
	it = 0;        // Iterationszähler
	itMax = 200;   // Maximale Anzahl Iterationen

	while (n < itMax && abs(Z) < 2)
	{
		Z = Z * Z + C;
		it++;
	}

	return it;
}

 

Da Computer komplexe Zahlen i.d.R. nicht direkt "verstehen" (es sei denn, man schreibt sich Funktionen oder Klassen dafür) – aber auch aus Performancegründen –, transformiert man in Programmen zum Zeichnen der Mandelbrot-Menge die Iterationsvorschrift Z->Z²+C meistens so um, dass "normale" reelle Zahlen verwendet werden können. Hierzu kann man die oben beschriebenen Formeln zum Rechnen mit komplexen Zahlen verwenden:


Schritt Gleichung Bemerkung
1 Zneu = Z * Z + C Iterationsvorschrift – Schreibweise mit komplexen Zahlen
2 Z = a + bi Komplexe Zahl Z mit Real- und Imaginärteil
3 C = x + yi Komplexe Zahl C mit Real- und Imaginärteil
4 aneu + bineu = (a + bi) * (a + bi) + x + yi Iterationsvorschrift – Schreibweise mit reellen Zahlen
5 aneu + bineu = (a * a) + (a * bi) + (bi * a) + (bi * bi) + x + yi Klammern ausmultiplizieren
6 aneu + bineu = + (ab)i + (ab)i + (bi)² + x + yi zusammenfassen
7 aneu + bineu = + (2ab)i - + x + yi weiter zusammenfassen; Achtung: i² = -1
8 aneu + bineu = a² - b² + x + (2ab + y)i nach Real- und Imaginärteil sortieren
9 aneu = a² - b² + x; bneu = 2ab + y Real- und Imaginärteil getrennt hinschreiben

 

Mit Z = a + bi und C = x + yi wird so aus der komplexen Form

die folgende, mit reellen Zahlen auskommende Form:

Damit kann der obige Algorithmus zur Mandelbrot-Rückkopplung nun folgendermaßen umgeschrieben werden:


function Iterate(x, y)
{
	a = 0, b = 0;           // Real- und Imaginärteil von Z (Startwert: Z=0)
	a2 = 0, b2 = 0;         // Quadrat von a und b merken (Performance!)
	it = 0;                 // Iterationszähler
	itMax = 200;            // Maximale Anzahl Iterationen

	while (it < itMax && a2 + b2 < 4)   // Anm.: |Z|² = a²+b² < 2²
	{
		b = 2 * a * b + y;  // Imaginärteil von Z berechnen
		a = a2 - b2 + x;    // Realteil von Z berechnen

		a2 = a * a;         // Quadrate von Real- und Imaginärteil merken,
		b2 = b * b;         // um sie nicht zweimal berechnen zu müssen (Performance!)

		it++;
	}

	return it;
}

 

Ein Programm, das ein 600×450 Pixel großes Bild der Mandelbrot-Menge mit der Ausdehnung -2,2 ... 1,0 (Realteil) und -1,2 ... 1,2 (Imaginärteil) zeichnet, würde dann in etwa so aussehen:


// Von- und Bis-Werte von Real- und Imaginärteil des darzustellenden Ausschnitts
reMin = -2.2, reMax = 1, imMin = -1.2, imMax = 1.2;

reStep = (reMax - reMin) / 600;          // Schrittweite des Realteils von Z pro Bildpunkt
imStep = (imMax - imMin) / 450;          // Schrittweite des Imaginärteils von Z pro Bildpunkt

for (yPos = 0; yPos < 450; yPos++)       // Schleife über alle Zeilen des Bildes
{
	y = imMax - yPos * imStep;           // Imaginärteil von C
	for (xPos = 0; xPos < 600; xPos++)   // Schleife über alle Spalten des Bildes
	{
		x = reMin + xPos * reStep;       // Realteil von C
		it = Iterate(x, y);              // Anzahl Iterationen für Punkt C ermitteln
		if(it == itMax)                  // Punkt C gehört zur Mandelbrot-Menge
		{
			farbe = schwarz;             // mit Farbe Schwarz zeichnen
		}
		else                             // Punkt C gehört nicht zur Mandelbrot-Menge
		{
			farbe = Palette[it];         // mit einer Farbe aus der Palette zeichnen
		}
		PutPixel(xPos, yPos, farbe);     // Pixel ausgeben
	}
}

 

Dies ist natürlich vereinfacht dargestellt, zeigt aber das Prinzip. Nachfolgend daher ein vollständiges, funktionsfähiges JavaScript-Programm zur Darstellung der Mandelbrot-Menge:



[Script in einem neunen Tab/Fenster öffnen]
 

Das Script ist mit ca. 250 Zeilen noch relativ übersichtlich und soll nur als Anschauungsbeispiel dienen, wie man ein Programm zur graphischen Darstellung der Mandelbrotmenge praktisch umsetzen könnte. Es verfügt über eine einfache Zoom-Funktion per Mausklick (Linksklick = vergrößern, Rechtsklick = verkleinern) und erlaubt die Auswahl von 3 Farben zur Erzeugung eines Farbverlaufs für die Randbereiche der Mandelbrot-Menge. Zur Vermeidung sichtbarer Farbabstufungen kann der Farbverlauf optional geglättet werden. Hierzu wird der bei der Mandelbrot-Iteration Z=Z²+C ermittelte Iteratorwert mit Hilfe der Formel "log(log(|Z|)) / log(2)" von einem ganzzahligen in einen gebrochenen Wert umgerechnet, der abhängig ist von der "Fluchtdistanz" bei Erreichen der Divergenz (nähere Erläuterungen dazu unter Smooth Shading for the Mandelbrot Exterior by Linas Vepstas). Für einen guten Glättungs-Effekt muss der Fluchtradius statt des üblichen Wertes von 2 mindestens 4, besser 5 betragen; darüberhinaus ist mit bloßem Auge kein Unterschied auszumachen.

Damit auch bei tieferem Hineinzoomen immer der volle Farbumfang im Bild enthalten ist und somit auch die feineren Strukturen möglichst kontrastreich dargestellt werden, teilt das Programm die Farbpalette dynamisch auf die im Bild tatsächlich vorkommenden Iteratorwerte auf. Beispiel: Bei einer maximalen Iterationszahl von 200 kann der Iteratorwert im Randbereich der Mandelbrot-Menge theoretisch 1...199 betragen (innerhalb der schwarz gezeichneten Mandelbrot-Menge beträgt er 200). Bei einer Ausschnittsvergrößerung ist der tatsächlich im Bild vorkommende Wertebereich des Iterators jedoch kleiner, z.B. 54...168. Anstatt nun den Wert 1 der Startfarbe Grün und den Wert 199 der Endfarbe Magenta fest zuzuordnen, wodurch der sichtbare Wertebereich 54...168 einen geringeren Farbumfang aufweisen würde, wird die Startfarbe Grün dem kleinsten im Bild vorkommenden Iteratorwert von 54 und die Endfarbe Magenta dem größten vorkommenden Iteratorwert von 168 zugeordnet. So wird erreicht, dass im Bild immer der vollständige Farbumfang enthalten ist.

Über eine Dropdown-Box kann zusätzlich noch die Linearität des Farbverlaufs bestimmt werden. Normalerweise "drängen" sich die Farben in der Nähe des Randes der Mandelbrot-Menge stärker als weiter weg davon, was zur Folge hat, dass die Umgebung der Mandelbrot-Menge etwas "einfarbig" erscheint. Je nach Auswahl einer Linearität wird der Farbverlauf mehr oder weniger stark "entzerrt" und der untere Bereich der Iteratorwerte – also die Bereiche, die sich weiter von der Mandelbrot-Menge entfernt befinden – farblich differenzierter dargestellt. Bei Auswahl von "logarithmisch" und "Gamma" kann über einen Schieberegler die Stärke der Entzerrung eingestellt werden.

Hinweis: Falls der Link "Quellcode ansehen" browserbedingt nicht funktionieren sollte, kann die Datei auch heruntergeladen werden. Hierzu einfach Rechtsklick auf den Link "Script in einem neunen Tab/Fenster öffnen" und "Ziel speichern unter..." auswählen (die Funktion kann browserabhängig auch etwas anders heißen).


Homepage > Apfelmännchen in JavaScript > Malen nach Zahlen