Parameters:
a = -1.24458; b = -1.25191; c = -1.815908; d = -1.90866;
Compiled function used for iteration:
cf = Compile[{{pt, _Real, 1}},
{Sin[a pt[[2]]] + c Cos[pt[[1]]],
Sin[b pt[[1]]] + d Cos[pt[[2]]]},
CompilationOptions -> {"InlineExternalDefinitions" -> True}
];
Iterate, rescale the result to the box {{0,1},{0,1}}
, histogram it, convert to an image, and finally apply a gamma adjustment (^0.5
) for better visibility.
im = ImageAdjust@Image@BinCounts[
Rescale@NestList[cf, {0., 0.}, 1000000],
1/500., 1/500.
];
ColorNegate[im^0.5]
You may want to throw in a Colorize
too.
With 10 million points and $\gamma = 1/3$, I get
Here's a LibraryLink implementation with LTemplate.
Create class and initialize with the the desired $a,b,c,d$ parameters and image width.
clifford = Make[CliffordAttractor];
clifford@"init"[{-1.8, -2.0, -0.5, -0.9}, 600]
Compute 50 million iterations. If the image is not of sufficient quality, more iterations can be computed without losing the old data.
clifford@"compute"[50000000] // AbsoluteTiming
(* {3.1645, Null} *)
Visualize with a custom colour function:
Colorize[
ImageAdjust[clifford@"image"[]]^0.1,
ColorFunction -> (Blend[{White, RGBColor[0.87, 0.94, 1], RGBColor[0.48, 0.33333, 0.66667], Red}, #] &)
]
Check the current $(x,y)$ value:
clifford@"state"[]
(* {-0.773525, 1.1536} *)
The library code follows. Admittedly, this could have been done with a single function that takes the parameters and returns an image. To refine the result, we could have averaged multiple returned images.
Here I wanted to demonstrate how to maintain a state within the library and update it or retrieve information about it as needed.
Needs["LTemplate`"]
SetDirectory[$TemporaryDirectory];
template =
LClass["CliffordAttractor",
{LFun["init", {{Real, 1, "Constant"} (* {a,b,c,d} *), Integer (* image width *)}, "Void"],
LFun["setState", {{Real, 1, "Constant"} (* {x,y} *)}, "Void"],
LFun["state", {}, {Real, 1}], (* get {x,y} *)
LFun["compute", {Integer (* iterations *)}, "Void"],
LFun["image", {}, Image]}
];
code = "
using namespace mma;
class CliffordAttractor {
double a = 0.0, b = 0.0, c = 0.0, d = 0.0;
double x = 0.0, y = 0.0;
ImageRef<float> *im = nullptr;
double w, h; // image half-width and half-height in real coordinates
void free() {
if (im) {
im->free();
delete im;
}
}
public:
~CliffordAttractor() { free(); }
void init(RealTensorRef param, mint size) {
massert(param.size() == 4);
a = param[0]; b = param[1]; c = param[2]; d = param[3];
w = 1+std::abs(c);
h = 1+std::abs(d);
free();
im = new ImageRef<float>(makeImage<float>(size, std::ceil(size * (h/w))));
std::fill(im->begin(), im->end(), 0.0);
}
void setState(mma::RealTensorRef state) {
massert(state.size() == 2);
x = state[0]; y = state[1];
}
mma::RealTensorRef state() const { return mma::makeVector<double>({x,y}); }
void compute(mint n) {
massert(im);
for (mint i=0; i < n; ++i) {
double newx, newy;
newx = std::sin(a*y) + c*std::cos(a*x);
newy = std::sin(b*x) + d*std::cos(b*y);
x = newx; y = newy;
(*im)( (im->rows()-1) * (y+h)/(2*h), (im->cols()-1) * (x+w)/(2*w) ) += 1;
}
}
GenericImageRef image() const { massert(im); return im->clone(); }
};
";
Export["CliffordAttractor.h", code, "String"];
CompileTemplate[template, "CompileOptions" -> {"-O3 -ffast-math"}]
LoadTemplate[template]