It's a simple grid; the adaptive threshold makes it not oversample pixels not different enough from their neighbors. Unfortunately, that results in cutting off the tips of Mandelbrot valleys. The grid also makes it vulnerable to moire. Depth makes it further subdivide multiple times if the threshold is not met. 3x3, threshold 0.0, depth 2 on a 1024x768 image is equivalent to computing a non-antialiased 9216x6912 image and downsampling it to 1024x768.
It can be made not a grid with a transform like this one:
Jitter {
; Jitters the pixel coordinates slightly to kill moire.
global:
float pixsize = 4*@jittermag/(#magn*#width)
transform:
int x = trunc(real(#screenpixel)*1000)
int y = trunc(imag(#screenpixel)*1000)
int x2 = random(x * y - 45783456)
int y2 = random(random(random(random(random((x - y)*(x + y) + 84358476)))))
float xx = (random(random(random(random(x2))))*0.5/#randomrange)*pixsize
float yy = (random(random(random(random(y2))))*0.5/#randomrange)*pixsize
#pixel = #pixel + xx + (0,1)*yy
default:
title = "Jitter"
param jittermag
caption = "Jitter magnitude (pixels)"
default = 1.0
min = 0.0
endparam
}This uses a noisy-but-deterministic function of the pixel coordinates (the *1000s are so even at 3x3 depth 4 this function will differ from one antialias sample point to the next) to perturb the calculation coordinates. With the default magnitude of 1.0 the perturbation is as much as half a pixel up or down and half a pixel left or right. The effect is for the antialiasing samples to be scattered randomly in and near the pixel instead of forming a neat grid.
Making the magnitude smaller moves the samples around less. If the antialiasing is 3x3 depth 4, there end up being 81 samples across and 81 down each pixel, so setting 0.02 will still fairly thoroughly smear away the grid while keeping the sample points mostly within the pixel and fairly smoothly distributed over its interior. Use 0.06 for depth 3, 0.18 for depth 2, and 0.5 for depth 1 for best moire-killing results.
The above only applies if using ultrafractal. Other tools (such as my own custom ones, or programs like ChaosPro) may do things differently and won't have the same way of specifying custom transforms (if they have any at all).
UF's transforms allow a few other hacks besides this one. Here's my favorite:
MandelSpeedup {
; Blots out the two largest parts of the Mandelbrot set.
; Speeds up Elephant Valley and Seahorse Valley.
transform:
IF (|1 + sqrt(1 - 4*#pixel)| <= 1)
#solid = true ; fixed point #1 is stable; inside cardioid
ELSEIF (|1 - sqrt(1 - 4*#pixel)| <= 1)
#solid = true ; fixed point #2 is stable; inside cardioid
ELSEIF (|#pixel + 1| <= 0.0625)
#solid = true ; inside period-2 bulb
ELSE
#pixel = #pixel
ENDIF
default:
title = "Mandelbrot Speedup"
}It masks off the cardioid and period-2 bulb of the M-set. Going for the hairier seahorses in between the regular ones along the valley wall, this can speed things up stupendously, particularly when you start having to use a maxiter of a billion or more. Of course it's useless if you want to color the interiors of these components of M, and it will not work for other fractals.
The cardioid is tested for by seeing if either fixed point is stable. Any point with a stable cycle is in the interior of M. The fixed points are solutions to
z2 +
c =
z, of which there are two. A fixed point of
z2 +
c is stable if the derivative at that point, 2
z, is within the unit disk; that means that the transformation
z2 +
c is contracting in a neighborhood of the fixed point, so a disk around the fixed point exists that contracts towards it, i.e. it's an attractor. (The test allows for equality, so gets the boundary of "indifferent" points as well; these still belong to M. In practice the only point exactly on the boundary likely to be hit precisely is 0.25, the very tip of Elephant Valley.)
The square-root expressions in the above code compute the roots of the fixed-point equation but double the answer by omitting to multiply in a factor of 1/2, thus yielding these derivatives. As it just so happens, the big cardioid of M is precisely the set of points where one (or both) of the fixed points is stable or indifferent.
The bulb is tested for in a far simpler manner: it is a perfectly circular disk of radius 0.25 centered on -1. The test against 0.0625 is because the ultrafractal || operator computes a
squared modulus, so we need to test against 0.25
2 rather than against 0.25 itself (or take a square root, which seems less elegant).
In theory, a more general, massive speedup based upon this is possible in custom software:
- As each pixel is encountered, apply all of an initially-empty set of component tests.
- If a pixel is not found to be an interior point by testing, but is caught by periodicity logic, calculate the exact period.
- Add to the component tests this period; the component tests are stored as a list of integers.
- To apply a test for a particular period p, first find roots of (...(z2 + c)2 + c...)2 + c (p repetitions) via Newton's method or whatever, then iterate these points p times each accumulating a derivative (as is done for e.g. distance estimator calculations) and if any of these lies within the closed unit disk, halt and consider c to lie in M. If none of them does, go on to the next test, and if none of these pass, go on to the standard M-set iterations.
Each Newton iteration, and the derivative calc, will require about as much computation as 2
p normal iterations. In practice, you'll need something better than Newton for finding "small-basin" roots if you want, say, that tiny period-47 minibrot and its even tinier buds to get sped up. (Anyone know a good method for enumerating all roots of a polynomial on the complex plane? You'd want to apply this to get rough approximations to all the roots, and then polish the roots with Newton.) All told you might need the equivalent of a few hundred or even a few thousand iterations per pixel. This would bog things down quickly if tests were naively added for every period seen computing an image; practically, you'd want it to track for each period seen how many pixels had that period and only if there were, say, 100 or more encountered, add a test for that period. You would also want to ignore large periods (e.g. over 100).
Smarter still might be to identify the specific root that's stable within a component. If you get say 10 pixels in a row with the same period and ending close to the same point, sample a few points of the image near there and see how the attractor moves; calculate a linear approximation to how one of the attractor points varies with
c and test that point's stability. This will blank out a chunk of the component, perhaps a bigger chunk than the disk you could blow away using a distance estimate.