Der Clifford-Attractor

vorhergehende Artikel in: Chaos Java Numerik
15.03.2025

Nach meinem letzten Ausflug in die Welt des Chaos und der nichtlinearen Systeme habe ich nun ein weiteres untersucht - und eine Frage ist immer noch offen...

Der Attractor wird durch folgende Gleichungen beschrieben:

Letztlich ist dieses System kein klassisch dynamisches sondern eher eine diskrete Map wie beispielsweise der Henon-Map.

Die Visualisierung erfolgt dabei letztlich durch eine diskrete Unterabtastung des kontimuierlichen Zustandsraumes und eine Akkumulation der Besuchshäufigkeiten jedes Punktes des Rasters. Um ein solches zweidimensionales Histogramm - denn um ein solches handelt es sich letztlich - mit einigem Gewinn darstellen zu können, hat sich eine Anzahl von Iterationen als günstig herausgestellt, die etwa der fünffachen Anzahl an Stützstellen im Raster entspricht. Bei einer Auflösung von 1600 mal 1200 Stützstellen - insgesamt 1 920 000 - also 10 000 000.

Mit dem Koordinatenursprung als Startpunkt und den folgenden Parametern

ergibt sich folgendes Aussehen des Histogramms (in grau das Ergebnis der Implementierung mittels C++, in Falschfarben das Ergebnis der Implementierung mittels Java):

Screenshot Darstellung des Systems als Ergebnis der Java-Implementierung

Screenshot Darstellung des Systems als Ergebnis der C++-Implementierung

//angepasst aus https://paulbourke.net/fractals/clifford/paul_richards/main.cpp
/*
        xn+1 = sin(a yn) + c cos(a xn)
        yn+1 = sin(b xn) + d cos(b yn)
*/

<iostream> <cmath> <vector>

using namespace std;

// Change params only in this block namespace { const int width = 1600; const int height = 1200; const int frames = 1; const int iters = 10000000; const int skipIters = 10;

double sensitivity = 0.002;

const double minX = -4.0; const double minY = minX * height / width; const double maxX = 4.0; const double maxY = maxX * height / width; const double minA = acos( 1.6 / 2.0 ); const double maxA = acos( 1.3 / 2.0 ); const double minB = acos( -0.6 / 2.0 ); const double maxB = acos( 1.7 / 2.0 ); const double minC = acos( -1.2 / 2.0 ); const double maxC = acos( 0.5 / 2.0 ); const double minD = acos( 1.6 / 2.0 ); const double maxD = acos( 1.4 / 2.0 ); };

class Color { public: double r, g, b; Color(const double &red = 0, const double &green = 0, const double &blue = 0) : r(red), g(green), b(blue) { } Color& operator+=(const Color &rhs) { r += rhs.r; g += rhs.g; b += rhs.b; return *this; } static Color createHue( double h ) { h *= 6.0; int hi = static_cast<int>( h ); double hf = h - hi; switch( hi % 6 ) { case 0: return Color( 1.0 , hf, 0.0 ); case 1: return Color( 1.0 - hf, 1.0, 0.0 ); case 2: return Color( 0.0 , 1.0, hf ); case 3: return Color( 0.0, 1.0 - hf, 1.0 ); case 4: return Color( hf, 0.0, 1.0 ); case 5: return Color( 1.0, 0.0, 1.0 - hf ); } return Color(); } Color operator+(const Color &rhs) const { return Color(*this) += rhs; } };

int main(void) { vector<Color> image( width * height ); const double a=1.7; const double b=1.7; const double c=0.6; const double d=1.2; const Color curCol = Color::createHue(0); double x = 0.0, y = 0.0; double xn = 0.0, yn = 0.0; int xi = 0, yi = 0;

for (int j = 0; j < iters; ++j) { xn = sin(a * y) + c * cos(a * x); yn = sin(b * x) + d * cos(b * y); x = xn; y = yn; if ( j < skipIters ) continue; xi = static_cast<int>( (x - minX) * width / (maxX - minX) ); yi = static_cast<int>( (y - minY) * height / (maxY - minY) ); if ( xi >= 0 && xi < width && yi >= 0 && yi < height ) { image[ xi + yi * width ] += curCol; } } clog << "\r"; clog << "\n";

cout << "P6\n" << width << " " << height << "\n" << "255\n";

double minr=100000000; double maxr=-100000000; double ming=100000000; double maxg=-100000000; double minb=100000000; double maxb=-100000000; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color &c = image[ x + y * width ];

if(c.r>maxr) maxr=c.r; if(c.r<minr) minr=c.r; if(c.g>maxg) maxg=c.g; if(c.g<ming) ming=c.g; if(c.b>maxb) maxb=c.b; if(c.b<minb) minb=c.b; } } unsigned char r=0; unsigned char g=0; unsigned char b=0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Color &c = image[ x + y * width ];

r = static_cast<unsigned char>( pow(c.r/maxr ,.15) * 255.0 ); g = static_cast<unsigned char>( pow(c.g/maxg ,.3) * 255.0 ); b = static_cast<unsigned char>( pow(c.b/maxb ,.3) * 255.0 );

cout << r << r << r; } } clog << minr << "\n" << maxr << "\n" << ming << "\n" << maxg << "\n" << minb << "\n" << maxb << "\n"; return 0; }

import de.netsysit.model.table.ColorGradientTableModel;
import de.netsysit.util.StopWatch;

import java.io.IOException; import java.nio.charset.Charset;

public class Clifford { public static void main(String[] args) throws IOException { int width=1600; int height=1200; int iters=10000000; int citers=width*height*10; int skip=10;

double minX = -4.0; double minY = minX * (double)height / (double)width;

double maxX = 4.0; double maxY = maxX * (double)height / (double)width;

double gamma=0.2;

double a=1.7; double b=1.7; double c=0.6; double d=1.2;

double[] image=new double[width*height];

double x = 0.0, y = 0.0; double xn = 0.0, yn = 0.0;

StopWatch sw=new StopWatch(true,true); for (int j = 0; j < iters; ++j) { xn = java.lang.Math.sin(a * y) + c * java.lang.Math.cos(a * x); yn = java.lang.Math.sin(b * x) + d * java.lang.Math.cos(b * y); x = xn; y = yn;

if ( j < skip ) continue; int xi = (int)( (x - minX) * width / (maxX - minX) ); int yi = (int)( (y - minY) * height / (maxY - minY) ); if ( ((xi >= 0) && (xi < width)) && ((yi >= 0) && (yi < height)) ) { image[ xi + yi * width ] += 1; } } System.out.println(sw.measure()); double minr= Double.MAX_VALUE; double maxr= Double.MIN_VALUE; double cr=0; for (int j = 0; j < image.length; ++j) { cr = image[j]; if (cr > maxr) maxr = cr; if (cr < minr) minr = cr; } ColorGradientTableModel cgtm=new ColorGradientTableModel(ColorGradientTableModel.Types.MAGMA); java.awt.Color color=null; java.io.FileOutputStream fos=new java.io.FileOutputStream("/tmp/clifford.ppm"); java.io.BufferedOutputStream pw=new java.io.BufferedOutputStream(fos); java.lang.String head="P6\n"+width + " " + height+"\n255\n"; pw.write(head.getBytes(Charset.forName("US-ASCII"))); for (int j = 0; j < image.length; ++j) { cr = image[j]; double transformed= java.lang.Math.pow(cr / maxr, gamma); color=cgtm.computeColor((float)transformed,false); pw.write(color.getRed()); pw.write(color.getGreen()); pw.write(color.getBlue()); } pw.close(); fos.close(); System.out.println(sw.measure()); } }

Der eine interessante Fakt, dessen Ursachen sich mir bisher nicht erschlossen haben ist die Tatsache, dass die pure Berechnung (abzüglich der Erzeugung des Ergebnisbildes) in Java deutlich schneller ist als in der C++-Variante: Die C++-Variante benötigt im Schnitt etwas über 700ms, während sich die Java-Variante mit im Schnitt unter 600ms zufriedengibt. Eigentlich kann das nur daran liegen, dass die Histogrammberechnung mit dem überladenen Operator in der Klasse Color die Performanz zunichte macht. Ich werde das vielleicht später nochmals analysieren...

Interessant könnte auch ein Test sein, in dem die Parameter stückweise variiert werden und man sich eine Animation des Ergebnisses ansehen kann...

Artikel, die hierher verlinken

Weitere Experimente mit dem Clifford-Attractor

29.03.2025

Ich berichtete hier bereits über Experimente mit dem Clifford-Attractor, allerdings war ich noch Experimente unter geringfügig geänderten Parametern schuldig...

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Synchronisierung von Lorenz-Systemen III

    23.10.2020

    Nachdem ich in einem vorhergehenden Artikel auf das Problem des kleinen Parameterraums im Zusammenhang mit der Nutzung synchronisierter chaotischer Systeme hingewiesen hatte will ixch hier untersuchen, wie sensibel solche Systeme auf Abweichungen der Parameterwerte zwischen treibendem und getriebenen System reagieren

    Weiterlesen

Neueste Artikel

  • Plugin zur Arbeit mit Markdown für NeoVim

    Ich habe neulich beschrieben, dass ich aktuell mehr und mehr bemerke, dass Dinge, für die ich in meinem NeoVim-Setup Plugins benutzt habe sehr gut auch mit Bordmitteln funktionieren.

    Weiterlesen
  • Raspbian Upgrade von 11 (Bullseye) nach 12 (Bookworm)

    Ich habe neulich wieder einmal eine Upgrade- und Backup-Sitzung mit meinen diversen Linuxinstallationen veranstaltet. Der Zeitpunkt schien mir gekommen, da es eine neue stable Variante von Debian (Trixie) gibt.

    Weiterlesen
  • Meine praktischen Erfahrungen mit ollama (llava)

    Ich diskutiere immer wieder gern über das was heute Machine Intelligence oder Artificial Intelligence ( oder wie die ganzen anderen hohlen Phrasen heißen, die dafür heutzutage als Buzzwords missbraucht werden). Das geschieht online, in meinem $dayjob oder auch privat. Meine Meinung steht fest: das ist alles Quatsch und steht in keiner Relation zum Nutzen

    Weiterlesen

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen Codeberg-Repositories findet man hier.