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

  • xBrowserSync in Docker

    29.03.2021

    Nachdem ich schon längere Zeit nicht mehr über neue Dienste in meinem Docker-Zoo berichtet habe, habe ich in der vergangenen Woche wieder einmal einen Neuzugang begrüßen dürfen...

    Weiterlesen

Neueste Artikel

  • Asymmetrische Kryptographie

    Ich habe mich mit der Idee schon länger getragen: Nochmal einen Rundumschlag zu asymmetrischer Kryptographie zu machen. Dabei werde ich mich auf Demonstrationen der einzelnen Konzepte und Operationen mit Beispielcode konzentrieren und zu jedem der vorgestellten Konzepte mehr oder weniger ausführlich bezüglich der Einsatzszenarien und Vor- und Nachteile Stellung beziehen

    Weiterlesen
  • Windows? Nur noch gegen Bezahlung!

    Ich habe mich nun völlig von Windows - der armseligen Ausrede für ein Computerbetriebssystem aus Redmond - abgenabelt

    Weiterlesen
  • Vergleich Analoger und Digitaler Identitäten

    Eine Präsentation zum besseren Verständnis der Konzepte hinter digitalen Identitäten

    Aktualisierung vom 16. März 2025

    Aktualisierung der Präsentation mit einem Beispiel aus einem Film der 1980er Jahre und Betonung des Fakts, dass das Subjekt überhaupt nicht bemerken muss, dass eine Identität erstellt wird...

    Aktualisierung vom 17. August 2025

    Ein weiteres Beispiel wurde hinzugefügt.

    Aktualisierung vom 30. März 2026

    Aktualisierung der Präsentation: Erläuterung der Möglichkeit, mehr als ein Zertifikat für dasselbe Schlüsselpaar auszustellen und Exkurs zu Transport Layer Security als Beispiel der Forderung des Vorweisens bestimmter Arten digitaler Identitäten.
    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.