Der Lorenz-Attraktor, eine Ikone der Chaos-Theorie, mit TigerJython
Das Abenteuer TigerJython geht weiter, heute mit einer Ikone der Chaos-Forschung, dem Lorenz-Attraktor. Als einer der ersten hatte 1961 Edward N. Lorenz, ein Meteorologe am Massachusetts Institute of Technology (MIT), erkannt, daß Iteration Chaos erzeugt. Er benutzte dort einen Computer, um ein einfaches nichtlineares Gleichungssystem zu lösen, das ein simples Modell der Luftströmungen in der Erdatmosphäre simulieren sollte. Dazu benutzte er ein System von sieben Differentialgleichungen, das Barry Saltzman im gleichen Jahr aus den Navier-Stokes-Gleichungen1 hergeleitet hatte. Für dieses System existierte keine analytische Lösung, also mußte es numerisch (das hieß wie damals und auch heute noch vielfach üblich in FORTRAN) gelöst werden. Lorenz hatte entdeckt, daß bei nichtperiodischen Lösungen der Gleichungen vier der sieben Variablen gegen Null strebten. Daher konnte er das System auf drei Gleichungen reduzieren:
\[ \begin{aligned} \frac{dx}{dt} &= -\sigma (y - z) \\ \frac{dy}{dt} &= (\rho - z)x - y \\ \frac{dz}{dt} &= xy - \gamma z \end{aligned} \]
Dabei sind \(\sigma = -10\), \(\rho = 40\) und \(\gamma = - \frac{8}{3}\). Die Parameter der Gleichung habe ich Herm19942 entnommen, Stew19933 gibt \(\rho = 28\) an, aber der Wert ändert nichts an dem Verhalten der Kurve und \(\rho = 40\) füllt das Fenster einfach besser aus. 😜
Nach ersten Läufen gab Lorenz die Anfangswerte nicht mehr sechstellig (das war damals der FORTRAN-Standard), sondern nur noch dreistellig ein, da er der Meinung war, daß eine Abweichung von unter einem Promille keine Bedeutung habe.
Dabei entdeckte er, daß diese Lösung nur noch in den Anfangswerten mit der Lösung der sechsstelligen Eingabe übereinstimmte. Alle weiteren Werte liefen beträchtlich auseinander. Lorenz grundlegende Erkenntnis war, daß dieses Phänomen kein Computerfehler, sondern eine prinzipielle Eigenschaft nichtlinearer Gleichungssysteme sei. Schon die geringeste Änderung in den Anfangsbedingungen kann eine komplett andere Lösung hervorbringen. Diese Beobachtung ging später als »Schmetterlingseffekt« (»Der Schlag eines Schmetterlingsflügels in Brasilien kann einen Tornado über Texas hervorrufen.«) in die Geschichte der Chaostheorie ein.
Da TigerJython keine eingebaute Bibliothek zur numerischen Lösung von Differentialgleichungen besitzt, habe ich das einfache Eulersche Polygonzugverfahren zur numerischen Berechnung herangezogen
und dabei konstant dt = 0.01
gesetzt. Das benötigt natürlich mehr Rechenkapazität, als sie Lorenz je zur Verfügung standen, aber trotz der größeren Genauigkeit ändert sich nichts am chaotischen Verhalten der Kurve. Für die Farbberechnung habe ich dieses mal nur den Farbwert (hue
) bei jeder Iteration geändert, Sättigung und Helligkeit bleiben konstant auf dem höchsten Wert. Das ergibt kräftige Farben, die von Rot über Orange nach Gelb und dann über Grün, Blau und Violett wieder zurück nach rot wandern. So kann man schön erkennen, daß die beiden »Flügel« des Attraktors immer wieder, aber für uns unvorhersehbar, durchlaufen werden.
Um das zu verdeutlichen, habe ich mit
ein wenig getrickst, damit der Farbbereich in Abhängigkeit von den Iterationsschritten der Schleife schneller und gegebenenfalls mehrfach durchlaufen wird.
Außerdem wollte ich, daß sich der Attraktor langsam aufbaut, damit Ihr die Entstehung der Kurve nachvollziehen könnt. Normalerweise zeichnet TigerJython alles sofort nach der Berechnung auf den Bildschirm, das heißt der Lorenz-Attraktor ist komplett schon nach Sekundenbruchteilen auf dem Monitor zu sehen. Eine schnelle Lösung wäre, bei jedem Iterationsschritt mit delay(ms)
4 eine Pause zu erzwingen. Das führt jedoch zu einem Flackern des Bildschirms. Dem kann man wiederum entgehen, indem man das Neuzeichnen in einem Hintergrundspeicher (Bildbuffer) ausführt und dann erst das fertige Bild in einem Schritt auf den Monitor herausrendert. Dieses Verfahren nennt man Doppelbufferung. Bei Animationen wird daher das automatische Rendern der einzelnen Graphikbefehle mit enableRepaint(False)
deaktiviert und der Bildbuffer zum geeigneten Zeitpunkt mit repaint()
gerendert.
gp.enableRepaint(False)
while(True):
# Berechne und zeichne das Bild im Hintergrundspeicher
gp.repaint() # Zeichne das fertige Bild auf den Monitor
gp.delay(5) # Fünf Millisekunden Pause
# …
Zu guter Letzt ist noch das Problem zu lösen, daß sich der klassische Lorenz-Attraktor aus drei unabhängigen Variablen zusammensetzt, TigerJythons GPanel aber nur zweidimensional Graphiken darstellen kann. Eine weitverbreitete Methode, dies zu lösen, ist, immer nur zwei Ebenen darzustellen. Angefangen habe ich mit der Darstellung des Attraktors in der x-z-Ebene:
Bei der Skalierung auf die Fenstergröße habe ich ein wenig geschummelt, um das Fenster jeweils bestmöglich auszufüllen. Für die x-z-Ebene habe ich diese Parameter genutzt:
In der y-z-Ebene waren es dann diese Parameter:
Und in der x-y-Ebene fand ich dann, daß diese Skalierungsparameter das Bildschirmfenster optimal ausfüllten:
Denn nicht nur das Hirn, sondern auch das Auge soll sich schließlich an den Ergebnissen erfreuen können. Dafür nimmt man dann schon einmal eine kleine Schummelei in Kauf. 🤓
Jetzt aber den kompletten Quellcode, damit Ihr auch alles nachvollziehen, nachprogrammieren, aber auch erweitern und ausbauen könnt:
import gpanel as gp
import math, colorsys
WIDTH = 640
HEIGHT = 480
sigma = -10.0
rho = 40.0
gamma = 8.0/3
x = y = z = 0.01
t = hue = 0.0
dt = 0.01
gp.makeGPanel(gp.Size(WIDTH, HEIGHT))
gp.window(0, WIDTH, HEIGHT, 0)
gp.windowPosition(1200, 50)
gp.bgColor(gp.Color(33, 41, 70))
gp.title("Lorenz-Attraktor, x-z-Ebene")
gp.enableRepaint(False)
gp.lineWidth(2)
while t < 74.9:
c = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
gp.setColor(c)
# Euler-Integration
dx = sigma*(x - y)*dt
dy = (x*(rho - z) - y)*dt
dz = (x*y - gamma*z)*dt
x += dx
y += dy
z += dz
# auf Fenstergröße skalieren
# Skalierungsparameter für die Fenstergröße:
# x-z-Ebene: xx = (x*8) + 320, zz = 470 - (z*5.5)
# y-z-Ebene: yy = (y*7) + 320, zz = 470 - (z*5.5)
# x-y-Ebene: xx = (x*8) + 320, yy = 240 - (y*5)
xx = (x*8) + 320
zz = 470 - (z*5.5)
if (t == 0.0):
gp.move(xx, zz)
else:
gp.draw(xx, zz)
gp.repaint()
gp.delay(5)
t += dt
hue = ((20*t)%100)/100.0
print("I did it, Babe!")
Wie bei (fast) allen meinen TigerJython-Experimenten gibt es den Quellcode natürlich auch in meinem GitLab-Repositorium.
Literatur
- Karl-Heinz Becker, Michael Dörfler: Kochrezepte für Fraktale. Computergrafische Experimente mit Python, Bremen 2024
- Sau Sheong Chang: Exploring Everyday Things with R and Ruby, Sebastopol (O’Reilly) 2012
- James Gleick: CHAOS – die Ordnung des Universums. Vorstoß in Grenzbereiche der modernen Physik, München (Knaur Taschenbuch) 1990
- Dietmar Hermann: Algorithmen für Chaos und Fraktale, Bonn (Addison-Wesley) 1994
- Frank Piefke: Simulationen mit dem Personalcomputer, Heidelberg (Hüthig) 1991
- Karline Soetaert, Peter M.J. Hermann: A Practical Guide to Ecological Modelling: Using R as a Simulation Platform, o.O. (Springer Netherlands) 2009
- Ian Stewart: Spielt Gott Roulette? Frankfurt (Insel TB) 1993
Einen ausführlichen Bericht von Eric W. Weisstein über den Lorenz-Attractor mit vielen weiteren Literaturhinweisen gibt es auf der Wolfram Mathworld.
Fußnoten
Eine sehr schöne Einführung in das ungelöste Problem der Navier-Stokes-Gleichungen findet Ihr bei Florian Freistetter in der 217. Folge seiner Sternengeschichten↩︎
Dietmar Hermann: Algorithmen für Chaos und Fraktale, Bonn (Addison-Wesley) 1994, S. 80ff.↩︎
Ian Stewart: Spielt Gott Roulette? Frankfurt (Insel TB) 1993, S. 141ff.↩︎
Der Parameter
ms
steht für Millisekunden.↩︎