Spielwiese

Experimente mit dem »Apfelmännchen«

[Einfärbung des "Innern"] [Weg der Zahl Z] [Z-Orbit-JavaScript-Anwendung] [Programmbeschreibung] [Z-Orbit-Galerie]

Einfärbung des "Innern"

Meistens wird das Innere des "Apfels", also die "im Kreis gefangenen" Punkte C auf der Gaußschen Zahlenebene, mit einer einheitlichen Farbe dargestellt, z.B. Schwarz. Wie man aber bei der Veranschaulichung der Mandelbrot-Iteration Z=Z²+C sehr schön erkennt, tut sich eine ganze Menge, bis feststeht, ob die komplexe Zahl Z zur Mandelbrot-Menge gehört oder nicht: Die Zahl Z "hüpft" während der Iteration wild auf der Gaußschen Zahlenebene wie wild umher – das müsste sich doch irgendwie zur Einfärbung nutzen lassen...

Beim "Sprungverhalten" der Zahl Z während der Iterationen fällt auf, dass es hierbei – abhängig von der Position der Startzahl C – unterschiedliche "Hauptmuster" gibt: Spiralen, Zyklen und chaotische Sprünge. Spiralen, die immer enger werden und auf ein Zentrum zusteuern, gibt es im "Hauptapfel". In den "Nebenäpfeln" gibt es Zyklen, also eine begrenzte Anzahl von Punkten, zwischen denen die Zahl Z immer wieder wechselt. Chaotische Sprünge treten naturgemäß außerhalb der Mandelbrot-Menge auf und enden früher oder später in Divergenz, also in der Unendlichkeit. In den Randbereichen gibt es auch "Mischungen" zwischen diesen Mustern, also z.B. "Chaotische Spiralen" oder "Chaotische Zyklen".



Sprungverhalten des Punktes Z: Spiralen, Zyklen und Chaos
 

Zyklenzahl

Vielleicht lässt sich ja die Anzahl der unterschiedlichen Positionen auf der Gaußschen Zahlenebene, die die Zahl Z während der Iterationen ansteuert, zur Einfärbung nutzen. Bei manchen Zyklen gibt es nur wenige Positionen, zwischen denen die Zahl Z abwechselnd hin- und herspringt, bei anderen mehr. Programmtechnisch ist das relativ einfach umzusetzen: Man muss nur während der Iterationen die Werte der Zahl Z in ein Hash bzw. ein assoziatives Array schreiben, so dass Duplikate unterbunden werden. Nach dem Ende der Iterationsschleife zählt man dann die Einträge in diesen Array und hat die Zahl der unterschiedlichen Positionen der Zahl Z. In der Praxis ist das in JavaScript leider alles andere als schnell, da Arrays in JavaScript leider sehr unperformant sind. Dennoch habe ich es einmal kurz ausprobiert:



 

Zur Einfärbung wurde hier eine "Regenbogenfarben-Palatte" benutzt (Rot-Gelb-Grün-Cyan-Blau-Violett-Rot). Wie man an dem Frabverlauf sieht, ist die Anzahl der unterschiedlichen Z-Positionen in der Mitte der "Bommel" am kleinsten (Rot) und wird zum Rand hin größer (Blau-Violett). Aufschlussreich, aber nicht wirklich hübsch...

Winkel

Man könnte vielleicht auch den Winkel zwischen der Zahl Z und der Startzahl C nach einer bestimmten Anzahl von Iterationen ermitteln und den Wert dann einer Farbe zuordnen.



Ermittlung des Winkels zwischen Z und C
 

Dies geht mit der erweiterten Arcus-Tangens-Funktion von JavaScript ganz einfach:


// Winkel von Z gegenüber C ermitteln
var alpha = Math.atan2(b - y, a - x);
if(alpha < 0) alpha += 2 * Math.PI;

// Winkel in Wert von 1...itMax-1 umwandeln
it = Math.floor((itMax - 1) * alpha / Math.PI / 2) + 1;

 

Diese Zeilen kommen ans Ende der Funktion Iterate() vor dem "return it;"

Hier zunächst ein Gesamtbild des »Apfelmännchen«:



Das "Innere des Apfels" wird abhängig vom Winkel zwischen Z und C eingefärbt.
 

Wie man sieht, werden durch die Einfärbung des Winkels die "Höhenlinien" als solches sichtbar. Da sich die Position von Z und somit der Winkel mit jeder Iteration ändert (außer im "Hauptapfel"), ändern sich auch die Farbverläufe, wenn man einen anderen Wert für die Maximalzahl der Iterationen wählt.

Auch bei stärkerem Hineinzoomen in die Randbereiche der Mandelbrot-Menge enthalten die "Höhenlinien" nun hübsche Farbverläufe:



Auch die "Höhenlinien" in den Randbereichen der Mandelbrot-Menge erhalten interessante Farbeffekte.
 

Weg der Zahl Z

Sichtbarmachung aller Z-Punkte

Spielt man ein wenig mit dem Script zur Veranschaulichung der Mandelbrot-Iteration Z=Z²+C herum, dann fällt auf, dass die Zahl Z während der Iterationen ziemlich viel auf der Gaußschen Zahlenebene "unterwegs" ist. Könnte man nicht alle Stellen innerhalb der Gaußschen Zahlenebene, an denen Z "vorbeikommt", als Bildpunkte sichtbar machen? Vielleicht so, dass die Helligkeit oder Farbe eines Bildpunktes abhängig ist von der Anzahl der "Besuche" durch eine Zahl Z während des gesamten Zeichenvorgangs eines »Apfelmännchens«? Das sollte doch programmtechnisch leicht umzusetzen sein: Man bräuchte dazu ein Array mit genausovielen Zählerwerten, wie das Bild Pixel hat. Immer wenn ein Z auf einen bestimmten Bildpunkt zu liegen kommt, wird der dazugehörige Zählerwert im Array erhöht. Zum Schluss wird aus den ermittelten Zählerwerten im Array das Bild erzeugt, indem die Werte im Array einer Farbe zugeordnet werden.

Mein erster Versuch war allerdings ziemlich enttäuschend: Man sah nur einen kleinen hellen Fleck, zwei ineinanderliegende große Kugeln und außenrum ganz schwach irgendwelche kleineren "Anhängsel":



Nicht viel zu sehen.
 

Offenbar hat der kleine, nur 1 Pixel umfassende helle Fleck links der Mitte mit dem RGB-Farbwert 255/255/255 die Durchschnittshelligkeit der übrigen Bereiche so stark "gedrückt", dass diese im Vergleich dazu zu dunkel erscheinen. Mit einem Grafikprogramm ließ sich zwar noch etwas mehr "herausholen", aber die dunklen Bereiche blieben weiterhin unterrepräsentiert bzw. waren schlicht nicht vorhanden, weil die dazugehörigen Zählerwerte im Verhältnis so klein waren, dass ihnen die dunkelste Farbe zugeordnet wurde. Um das zu korrigieren, lag es daher nahe, für die Zuordnung des ermittelten Zählerwertes zu einem Farbwert eine nichtlineare Funktion zu verwenden, also eine Kurve, die zuerst steil nach oben geht und dann gegen Ende hin immer flacher wird. In der Regel verwendet man zur visuellen Darstellung sehr großer Wertebereiche eine logarithmische Skala, d.h. die darzustellenden Werte werden als Exponenten der Zahl 10 dargestellt. So wird z.B. der Wert 1 zu 0, 10 zu 1, 100 zu 2, 1000 zu 3, usw. Dadurch wird der Wertebereich so "komprimiert", dass sowohl sehr kleine Werte noch differenziert dargestellt werden können als auch sehr große Werte noch Platz im Diagramm finden. Ein Anwendungsbeispiel wären Signalpegel, die oft in Dezibel (dB) angezeigt werden: dB = 20×log(Verstärkungsfaktor). Hier also das Ergebnis mit logarithmischem Helligkeits- bzw. Farbverlauf:



Parameter: Max. Iterationen: 5000. Nur woher kommen die Linien?
 

Na also, schon besser! Interessante Formen – ein großer Kreis mit vielen "Birnen" drumherum... oder sind das Eier? Es würde jedenfalls zu Ostern passen, das an dem Wochenende war, als ich diese Versuche gemacht habe. ;-) Seltsam waren allerdings die Linien im Bild, die etwas wie "Spinnennetze" aussehen. Ist das ein Moiré-Efekt, oder g'hört das so? Hat es vielleicht mit der Rechenungenauigeit der Fließkommazahlen zu tun? Mein erster Ansatz, um den Moiré-Efekt wegzubekommen, war, die Pixelpositionen der ermittelten Zahlen Z mit einem Zufallswert zwischen -1 und +1 zu überlagern. Hierdurch entsteht zwar Rauschen und Unschärfe, aber das Bild ist insgesamt "ruhiger" und detailreicher:



Parameter: Max. Iterationen: 5000. Die Linien wurden durch Zufallsüberlagerung "verwischt".
 

Trotzdem blieb zunächst die Frage, warum einige eng umgrenzte Positionen scheinbar "bevorzugt" von der Zahl Z "besetzt" werden, wie ich an den kleinen "Pünkchen" zu erkennen glaubte. Bis mir dann ein "Licht aufging": Diese "Artefakte" entstehen durch das Bildraster! Zur Ermittlung der Iterations-Startzahlen C war ich ja – wie beim herkömmlichen »Apfelmännchens« – spalten- und zeilenweise die einzelnen Bildpunkte durchgegangen und hatte zu jedem Bildpunkt den Real- und Imaginärteil der komplexen Zahl C ermittelt und damit dann den Iterationsvorgang gestartet. Alle 1200×900 = 1,08 Millionen Startzahlen C waren also schön der Reihe nach am Bildraster ausgerichtet. Dieses "Eingangsraster" ergab dann ein (verzerrtes) "Ausgangsraster". Was also tun? Die logische Konsequenz war, die Startzahlen C nicht am Bildraster auszurichten, sondern – ja, wie eigentlich? Man konnte ja schlecht eine Schleife über alle Punkte in der Gaußschen Ebene bauen, von -2,2 bis +1,0 auf der realen Achse und von -1,2 bis +1.2 auf der imaginären Achse. Welche Abstände sollte man da nehmen, und was würde das bringen, denn bei festen Abständen hätte man ja wieder ein Raster, das Artefakte hervorbringt. Vielleicht die Abstände zufällig variieren, so wie ich das schon mit den Bildpunkten gemacht hatte? Dann könnte man ja gleich alle Startzahlen komplett per Zufall ermitteln! – Ja, warum eigentlich nicht? Immerhin gäbe es dann kein Raster mehr, das sich in Punkthäufungen oder Moiré-Mustern bemerkbar macht. Allerdings würde dann sicherlich auch ein Bildrauschen entstehen, das aber vermutlich mit zunehmender Anzahl von Zufallspunkten abnehmen würde, weil es dann statistisch "ausgemittelt" werden würde, ähnlich wie wenn man mehrere Standbilder eines verrauschten Videos überlagert, um ein rauschfreieres Bild zu erhalten. Die Frage war also, wieviele Zufallspunkte man für ein ausreichend detailreiches und weitestgehend rauschfreies Bild benötigt. Ich ersetzte also erstmal die zwei ineinandergeschachtelten Schleifen für die Bildspalten und -zeilen durch eine einzige Schleife mit einer variablen Anzahl von Durchläufen und bestimmte den Realteil x und den Imaginärteil y der komplexen Zahl C per Math.random(). Dabei stellte sich heraus, dass gute (detailreiche und rauscharme) Bilder mit einer Größe von 1200×900 Pixeln erst ab etwa 100 Millionen Zufallspunkten mit je 1000 maximalen Iterationen entstehen. Das hört sich nach einer Menge Holz an, und in der Tat war mein Rechner damit auch ca. 5 Minuten beschäftigt (allerdings nur auf einem CPU-Kern). Hier das Ergebnis – zuerst mit 10 Millionen Zufallspunkten (was zu wenig war), und dann mit 100 Millionen Punkten:



Parameter: Max. Iterationen: 1000, Anzahl Zufallspunkte: 10 Millionen
10 Millionen Zufallspunke sind anscheinend noch zu wenig für ein rauschfreies Bild.
 



Parameter: Max. Iterationen: 1000, Anzahl Zufallspunkte: 100 Millionen
Bei 100 Millionen Zufallspunken wird das Bild dann endlich einigermaßen rauschfrei.
 

Fazit: Sieht auf jeden Fall interessant aus, dieses Gebilde! Die Grundform des »Apfelmännchens« ist noch klar zu erkennen, aber die Ausgestaltung ist ganz anders. In dem »Apfelmännchen« steckt also noch Einiges an Potenzial drin! Leider ist es mit dem verwendeten Algorithmus nicht möglich, tiefer in der Bild "hineinzuzoomen" und Ausschnitte stark vergrößert darzustellen, da die Startpunkte C der im Ausschnitt liegenden Z-Punkte ja auch außerhalb des Ausschnitts liegen können, d.h. man müsste trotzdem immer das gesamte Bild berechnen, und dies mit umso mehr Zufallspunkten, je weiter man hineinzoomt. Faktisch (und von der Rechenzeit her) käme ein Vergrößerungsfaktor x somit einer x-fachen Vergrößerung des Gesamtbildes gleich.

Weniger ist mehr: Beschränkung auf die "Fluchtmenge"

Gibt es da vielleicht noch Optimierungsmöglichkeiten hinsichtlich einer Reduzierung der Berechnungszeit? Könnte man nicht z.B. einfach einen Teil der Z-Punkte weglassen, beispielsweise diejenigen, deren Startpunkte C innerhalb der Mandelbrot-Menge liegen, wo die meiste Rechenzeit verbraten wird? – Nein, das geht natürlich nicht, denn man weiß ja erst hinterher nach Beendigung der Iteration, ob ein Startpunkt C zur Mandelbrot-Menge gehört oder nicht. Man man muss also tatsächlich immer alle Startpunkte durchiterieren. Trotzdem: Wie sähe das Bild wohl aus, wenn man nur die Z-Punkte abbildet, deren Startpunkte C nicht zur Mandelbrot-Menge gehören, sondern zur "Fluchtmenge"? Beim normalen »Apfelmännchen« sind ja auch die Bereiche außerhalb bzw. ganz nah am Rand der Mandelbrot-Menge am interessantesten, weil dort das "Sprungverhalten" der Z-Punkte am "chaotischsten" ist. Das Programm dürfte dann allerdings die Z-Punkte nicht sofort in das Zähler-Array schreiben, sondern müsste sie solange zwischenspeichern, bis feststeht, ob die zum Startpunkt C gehörende Folge von Z-Punkten divergiert oder konvergiert. Nur wenn sie divergiert, wenn sie also nach einer bestimmten maximalen Anzahl von Iterationen den Einheitskreis mit Fluchtradius 2 verlässt, erst dann darf die zwischengespeicherte Folge von Z-Punkten in das Array mit den Zählerwerten für die Häufigkeit des Auftretens einer Zahl Z an einem bestimmten Bildpunkt übernommen werden. Nach Abschluss der Iterationen aller x-Millionen Zufalls-Startpunkte mit divergierenden Z-Folgen wird dann wie bisher aus den ermittelten Zählerwerten das Bild aufgebaut.

Nachfolgend einige der mit dieser Methode erzielten Ergebnisse – ehrlich gesagt, mit solchen Bildern hätte ich nicht gerechnet! Zur Erzeugung der Bilder wurde dieses Programm benutzt. Da die Bilder trotz des nichtlinearen Farbverlaufs zum Teil noch etwas kontrastarm waren, habe ich sie anschließend noch mit "Paint Shop Pro 7" nachbearbeitet, d.h. das Histogramm so angepasst, dass die verfügbare Bandbreite der Bildhelligkeit besser ausgenutzt wird – sprich Kontrast und Helligkeit wurden verbessert. Die zur Erzeugung benutzen Parameter für das Programm sind jeweils unter den Bildern angegeben.



Parameter: Max. Iterationen: 20, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: 1/4-sinusförmig
Erinnert irgendwie an Herbstlaub...
 



Parameter: Max. Iterationen: 150, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: 1/4-sinusförmig
Ein Röntgenbild des »Apfelmännchens«? :-)
 

Wie man sieht, hängt das Aussehen und die sichtbaren Strukturen stark von der maximalen Anzahl der Iterationen und von der Anzahl der berechneten Startpunkte ab. Der Fluchtradius hat dagegen keinen Einfluss auf den Bildinhalt.

Die folgenden 3 Bilder haben dieselbe Anzahl maximaler Iterationen (1000) und Zufalls-Startpunkte (100 Millionen). Der einzige Unterschied ist die Linearität des Farbverlaufs (linear, 1/4-sinusförmig, logarithmisch).



Parameter: Max. Iterationen: 1000, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: linear
 



Parameter: Max. Iterationen: 1000, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: 1/4-sinusförmig
Wie oben aber 1/4-sinusförmiger Farbverlauf. Details kommen besser raus.
 



Parameter: Max. Iterationen: 1000, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: logarithmisch
Bei logarithmischem Farbverlauf sieht es ziemlich "feurig" aus... Ist das ein verglühender Satellit? :-)

 

Mit logarithmischem Farbverlauf haben die Bilder am meisten Kontrast. In Kombination mit den passenden Farben (hier schwarz, dunkelorange und weiß) entsteht der Eindruck von glühenden oder brennenden Objekten, was schon recht "spektakulär" aussieht...

Nun eine Folge von Bildern mit zunehmender Iterationszahl (5.000, 20.000 und 1 Million). Bei 1 Million maximalen Iterationen habe ich allerdings weniger Zufalls-Startpunkte benutzt, da sonst die Rechenzeit zu lang geworden wäre. Die geringere Anzahl von Zufallspunkten wird ja durch die höhere Zahl maximaler Iterationen zum Teil auch wieder ausgeglichen. Für die Berechnung der 1 Million Zufallspunkte benötigte mein betagter Rechner etwa eine Stunde, und für 10 Million Zufallspunkte bereits 8 Stunden – da fühlt man sich fast in C64-Zeiten zurückversetzt... Bei den Bildern mit 1 Million maximalen Iterationen von 1 Million Punkten hat sich auch das Bildrauschen wieder etwas mehr bemerkbar gemacht, allerdings entstanden da auch interessantere Formen!



Parameter: Max. Iterationen: 5000, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: 1/4-sinusförmig
Sieht erst mal nicht viel anders aus als bisher.
 



Parameter: Max. Iterationen: 20.000, Anzahl Zufallspunkte: 100 Millionen, Farbverlauf: 1/4-sinusförmig
Auch hier tut sich noch nicht viel am Aussehen.
 



Parameter: Max. Iterationen: 1 Million, Anzahl Zufallspunkte: 1 Million, Farbverlauf: logarithmisch
Bei 1 Million Iterationen entstehen auf einmal seltsame "Objekte" und das Ganze wird asymmetrisch.
Ein wenig erinnert mich dieses Gebilde an den antiken " Mechanismus von Antikythera"... :-)
 



Parameter: Max. Iterationen: 1 Million, Anzahl Zufallspunkte: 10 Millionen, Farbverlauf: logarithmisch
Wie oben, nur zehnmal soviele Zufallspunkte. Es entsteht ein komplett anderes Bild.
 

Interessant finde ich bei den letzten Bildern mit max. 1 Million Iterationen die seltsamen Strukturen und die starke Asymmetrie. Normalerweise sind Mandelbrot-Grafiken ja immer symmetrisch zur reellen Achse – warum hier nicht? Das konnte ich mir zunächst nicht erklären. Wirkt sich bei einer so hohen Iterationszahl vielleicht doch die begrenzte Genauigkeit der 64-Bit-Fließkommazahlen von JavaScript aus? Oder lag es an Zufallszahlen, die vielleicht nicht "zufällig genug" sind? Auf die richtige Spur bin ich dann durch eine Nachfrage in einem englischsprachigen Forum zum Thema Fraktale (fractalforums.com) gekommen: Broken symmetry. Dort äußerte jemand die Vermutung, dass die seltsamen ringförmigen Objekte eigentlich die Bahnen sehr langer Folgen von Z-Punkten sind, deren Pfade immer sehr nahe beieinanderliegen und dadurch diese lokalen Strukturen erzeugen. Ob solche langen Folgen aus knapp 1 Million Z-Punkten vorkommen, und wo genau deren Bahnen verlaufen, hängt von der zufällig gewählten Startzahl C ab. Dies würde auf jeden Fall die Unregelmäßigkeit und die Asymmetrie erklären, und auch, dass die Bilder trotz gleicher Parameter immer anders aussehen.

Zur Verifizierung dieser Theorie habe ich dann mal mein Script zur Veranschaulichung der Mandelbrot-Iteration Z=Z²+C dahingehend modifiziert, dass anstelle der roten Verbindungslinien zwischen den Z-Punkten die Punkte selbst gezeichnet werden – und tatsächlich: die dabei entstehenden Muster für Startpunkte nahe am Rand der Mandelbrot-Menge haben tatsächlich eine starke Ähnlichkeit mit den ringförmigen Strukturen in den Bildern:



Links: Simulation (C=0,187+0,553i, Divergenz nach 16247 Iterationen)
Rechts zum Vergleich: Ausschnitt aus dem 2. Bild über diesem
 

Mittlerweile habe ich auch das Script zur Darstallung der Z-Punkt-Folgen (auch "Orbits" genannt, also "Bahnen") um eine Funktion zur Protokollierung der Zufalls-Startpunkte C erweitert. Man kann also nun einzelne Startpunkte auswählen, deren dazugehörigen Sequenzen von Z-Punkten dann gezeichnet werden. Einige Screenshots und Beispiele von "Z-Orbits" sind in einer kleinen Galerie zu sehen (in Englisch). Außerdem gibt es noch eine Beschreibung des Programms in Englisch.

Das Rätsel um die sonderbaren Strukturen war also gelöst. Dennoch finde ich es wieder einmal faszinierend, was für interessante und abwechslungsreiche Bilder aus so einem einfachen Algorithmus entstehen können. Durch das Zufalls-Element gleicht kein Bild dem anderen – da wartet man dann gerne auch mal ein Stündchen, um sich überraschen zu lassen, was sich der Künstler "Adam Riese" da wohl mal wieder ausgedacht hat... ;-) Als Beispiel hier noch zwei weitere Bilder, die mit genau denselben Parametern wie das vorletzte Bild weiter oben erzeugt wurden:



Parameter: Max. Iterationen: 1 Million, Anzahl Zufallspunkte: 1 Million, Farbverlauf: logarithmisch
 



Parameter: Max. Iterationen: 1 Million, Anzahl Zufallspunkte: 1 Million, Farbverlauf: logarithmisch
 

Im o.g. Forum schrieb jemand, dass es sich bei dieser Art von Fraktalgrafik um ein sogenanntes "Buddhabrot" handelt – was nichts mit dem ähnlich klingenden "Butterbrot" zu tun hat, sondern laut Wikipedia eine seit 1993 bekannte Methode zur Darstellung der Mandelbrotmenge ist, die, wenn man das Gebilde um 90° nach rechts dreht, an einen sitzenden Buddha erinnert. Nun ja, zumindest für die Bilder mit deutlich weniger als 1 Million Zufalls-Startpunkten ist eine gewisse Ähnlichkeit in der Tat nicht zu leugnen. ;-)

Durch die Diskussion in dem genannten Forum habe ich noch einige weitere Anregungen erhalten, die ich vielleicht demnächst mal programmtechnisch umsetzen und ausprobieren werde:

Weiterhin:

(To be continued...)


Homepage > Apfelmännchen in JavaScript > Spielwiese