„Wolken sind keine Kugeln, Berge keine Kegel, Küstenlinien keine Kreise. Die Rinde ist nicht glatt – und auch der Blitz bahnt sich seinen Weg nicht gerade.“ Über diesen Ausspruch von Benoît Mandelbrot dem Begründer der fraktalen Geometrie bin ich vor kurzem wieder gestolpert. Vor vielen Jahren hatte ich die von ihm entdeckte Mandelbrotmenge mit einem Pascal Programm berechnet, heute möchte ich einen kurzen Programmcode in Python vorstellen.
Die Mandelbrot-Menge ist verblüffend einfach definiert. Sie ist die Menge aller komplexen Zahlen c, für welche die durch
rekursiv definierte Folge komplexer Zahlen mit dem Anfangsglied z_0 = 0 beschränkt bleibt. Man kann leicht zeigen, dass z_n über jede Grenze wächst, sobald ein z_n > 2 auftritt. Die Menge der Zahlen für die die Folge beschränkt bleibt wird in der Regel schwarz dargestellt. Die Menge der Zahlen für die die Folge divergiert wird abhängig von ihrer Fluchtgeschwindigkeit farbcodiert, d.h. diese Zahlen bekommen, abhängig davon wieviel Iterationen benötigt wurden um die Folge auf Werte > 2 wachsen zu lassen, unterschiedliche Farben.
Die Darstellung unten zeigt die Mandelbrotmenge um die Zahl (-0.65,0i).
Für die graphische Darstellung habe ich die bekannte Bildverarbeitungsbibliothek 'Pillow' verwendet.
from PIL import Image
import colorsys
import math
Sie lässt sich unter Windows mit Hilfe der PowerShell und 'pip install pillow' installieren.
Als Parameter, werden die Breite des Bildausschnittes in Pixel, ein Punkt in der komplexen Ebene, der zu berechnende Bereich auf der reellen x-Achse und das Aspektverhältnis angegeben. 'Precision' gibt die Zahl der Iterationen an die maximal berechnet werden.
#frame parameters
width = 1000 #pixels
x = -0.65
y = 0
xRange = 3.4
aspectRatio = 4/3
precision = 500
Daraus errechnen sich alle anderen relevanten Größen.
height = round(width / aspectRatio)
yRange = xRange / aspectRatio
minX = x - xRange / 2
maxX = x + xRange / 2
minY = y - yRange / 2
maxY = y + yRange / 2
Für die Darstellung der Fluchtgeschwindigkeit habe ich Farbcodierungen gewählt, die einer logarithmischen bzw. einer Potenzfunktion folgen.
def logColor(distance, base, const, scale):
color = -1 * math.log(distance, base)
rgb = colorsys.hsv_to_rgb(const + scale * color,0.8,0.9)
return tuple(round(i * 255) for i in rgb)
def powerColor(distance, exp, const, scale):
color = distance**exp
rgb = colorsys.hsv_to_rgb(const + scale * color,1 - 0.6 * color,0.9)
return tuple(round(i * 255) for i in rgb)
Zuerst wird ein schwarzes Bild erzeugt.
img = Image.new('RGB', (width, height), color = 'black')
pixels = img.load()
Dann durchlaufen wir jeden Pixel des Bildes, und weisen jedem Pixel einen kartesischen Punkt zu. Für diesen Punkt berechnen wir die Folge, und weisen dem Pixel abhängig von der bis zum Abbruchkriterium 'x*x + y*y > 4' benötigten Iterationen eine Farbe zu.
for row in range(height):
for col in range(width):
x = minX + col * xRange / width
y = maxY - row * yRange / height
oldX = x
oldY = y
for i in range(precision + 1):
a = x*x - y*y #real component of z^2
b = 2 * x * y #imaginary component of z^2
x = a + oldX #real component of new z
y = b + oldY #imaginary component of new z
if x*x + y*y > 4:
break
if i < precision:
distance = (i + 1) / (precision + 1)
rgb = powerColor(distance, 0.2, 0.27, 1.0)
pixels[col,row] = rgb
index = row * width + col + 1
print("{} / {}, {}%".format(index, width * height, round(index / width / height * 100 * 10) / 10))
Anschließend wird das so erzeugte Bild als 'output.png' gespeichert.
img.save('output.png')
Zu guter Letzt noch ein zoom um (-1.2,0.4i) mit xRange 0.3 in logarithmischer Farbskala.
Man sieht bereits neue 'Apfelmännchen' in den Seitenarmen erscheinen. Viel Spaß beim selber Experimentieren mit der Mandelbrotmenge...
Keine Kommentare:
Kommentar veröffentlichen