Newton fractals
About the code
Definition
xn+1 = xn - f(xn) / f'(xn)
Where f'(x) is the derivative of the function f(x).
f(z) = z3 - 1
But we will see later that we can try with other functions.
f'(z) = 3z2
And we need to know its roots:
z1 = 1
z2 = -0.5 + i * √3 / 2
z3 = -0.5 - i * √3 / 2
On the english Newton fractal wikipedia page there is a sample code chunk to draw it.
#define MAX_ITERATIONS 100
#define TOLERANCE 1e-6
Then we define our function and its derivative.
// the function
Complex f(Complex z)
{
// z^3 - 1
static Complex one(1, 0);
return z * z * z - one;
}
// derivative of the function
Complex df(Complex z)
{
// 3 * z^2
return (z * z) * 3;
}
In the main function we will first define some value to tell where the window will be centered and what will be
static double zoom = 2.0 / (double)SCREEN_HEIGHT;
static double centerX = (double)SCREEN_WIDTH / 2.0;
static double centerY = (double)SCREEN_HEIGHT / 2.0;
We will need the roots of the function:
// roots of the function
Complex roots[3];
roots[0] = Complex(1, 0);
roots[1] = Complex(-0.5, sqrt(3) / 2);
roots[2] = Complex(-0.5, -sqrt(3) / 2);
Each pixel will take a color according to the root we reached in the iteration loop.
// palette
static Color palette[4];
palette[0] = Color(255, 0, 0);
palette[1] = Color(0, 255, 0);
palette[2] = Color(0, 0, 255);
palette[3] = Color(0, 0, 0);
Now we will draw the fractal. So we begin a loop over each pixels of the screen:
// draw the fractal
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
for (int x = 0; x < SCREEN_WIDTH; x++)
{
We convert the pixel's coordinates to a complex number:
// convert the pixel pos to a complex number
Complex z;
z.re = (x - centerX) * zoom - 0.75;
z.im = (y - centerY) * zoom;
We will use a variable to store the root we reached. By default it's on the last palette color.
// by default the pixel is black if the calculations fail
int color = 3;
We start the iteration loop
// iterate
int iter;
for (iter = 0; iter < MAX_ITERATIONS; iter++)
{
Here is the Newton's formula
z = z - f(z) / df(z);
We loop on every root in the array to check if we reached one
// are we near a root ?
for (int i = 0; i < 3; i++)
{
Complex delta = z - roots[i];
double distance = delta.squareMag();
If we reached a root, we store its number and we break out of the roots checking loop.
if (distance < TOLERANCE)
{
color = i;
break;
}
}
And we break out of the iteration loop too:
if (color != 3)
break;
}
And finally we draw the pixel.
// draw the pixel
gfx.setPixel(x, y, palette[color]);
As the drawing could take time, at the end of each line, we will render it and process the events to check if the
}
gfx.render();
sys.processEvents();
if (sys.isQuitRequested() == true)
break;
}
At the end we add a loop to wait for the user to close the window if the drawing is done.
// wait until we quit
while (sys.isQuitRequested() == false)
{
sys.processEvents();
sys.wait(16);
}
gfx.quit();
return EXIT_SUCCESS;
Download source code
Download executable for Windows
And this is the result:
Improving the colors
// draw the pixel
Color color = palette[palIdx];
if (palIdx != 3)
{
float coef = 1 - log(iter + 1) / 5.0;
if (coef < 0)
coef = 0;
color.r *= coef;
color.g *= coef;
color.b *= coef;
}
gfx.setPixel(x, y, color);
Download source code
Download executable for Windows
I compute a coefficient from the logarithm of the number of iterations.
f(z) = z3 - 2z + 2
// the function
Complex f(Complex z)
{
// z^3 - 2z + 2
return z * z * z - z * 2 + Complex(2, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 3 * z^2 - 2
return (z * z) * 3 - Complex(2, 0);
}
[...]
// roots of the function
Complex roots[3];
roots[0] = Complex(-1.7693, 0);
roots[1] = Complex(0.88465, 0.58974);
roots[2] = Complex(0.88465, -0.58974);
Download source code
Download executable for Windows
I also changed the colors and the shading coefficient to get closer than the source image, and this is what we get:
f(z) = z8 + 15z4 - 16
// the function
Complex f(Complex z)
{
// z^8 + 15z^4 - 16
Complex z4 = z*z*z*z;
return z4 * z4 + z4 * 15 - Complex(16, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 8z^7 + 60z^3
Complex z3 = z*z*z;
Complex z4 = z3 * z;
return (z4 * z3) * 8 + z3 * 60;
}
[...]
// roots of the function
Complex roots[8];
roots[0] = Complex(-1, 0);
roots[1] = Complex(1, 0);
roots[2] = Complex(0, -1);
roots[3] = Complex(0, 1);
roots[4] = Complex( 1.4142, 1.4142);
roots[5] = Complex(-1.4142, 1.4142);
roots[6] = Complex( 1.4142, -1.4142);
roots[7] = Complex(-1.4142, -1.4142);
Download source code
Download executable for Windows
f(z) = z5 - 3iz3 - (5+2i)z2 + 3z + 1
// the function
Complex f(Complex z)
{
// z^5 - 3iz^3 - (5+2i)z^2 + 3z + 1
return z * z * z * z * z - Complex(0, 3) * z * z * z - Complex(5, 2) * z * z + z * 3 + Complex(1, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 5z^4 - 9iz^2 - (10+4i)z + 3
return z * z * z * z * 5 - Complex(0, 9) * z * z - Complex(10, 4) * z + Complex(3, 0);
}
[...]
// roots of the function
Complex roots[5];
roots[0] = Complex(-1.28992, -1.87357);
roots[1] = Complex(-0.824853, 1.17353);
roots[2] = Complex(-0.23744, 0.0134729);
roots[3] = Complex(0.573868, -0.276869);
roots[4] = Complex(1.77834, 0.963437);
Download source code
Download executable for Windows
f(z) = z6 + z3 - 1
// the function
Complex f(Complex z)
{
// z^6 + z^3 - 1
Complex z3 = z * z * z;
return z3 * z3 + z3 - Complex(1, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 6z^5 + 3z^2
return z * z * z * z * z * 6 + z * z * 2;
}
[...]
// roots of the function
const int nbRoots = 6;
Complex roots[nbRoots];
roots[0] = Complex(-1.17398499670533, 0);
roots[1] = Complex(0.851799642079243, 0);
roots[2] = Complex(0.586992498352664, 1.016700830808605);
roots[3] = Complex(0.586992498352664, -1.016700830808605);
roots[4] = Complex(-0.425899821039621, -0.737680128975117);
roots[5] = Complex(-0.425899821039621, 0.737680128975117);
Download source code
Download executable for Windows
Variant of the formula
zn+1 = zn - a * f(zn) / f'(zn)
With this formula the roots of the function don't match anymore.
By Asympt (talk) - self-made, Public Domain, Link
// draw the pixel
Color color;
color = Color(0, 0, 0);
float angle = z.arg();
if (angle <= M_PI / 3)
color.r = 127 + 128 * sin(-angle);
if (angle >= -M_PI * 2/3 && angle <= M_PI * 2/3)
color.g = 255 * sin(angle * 3/4 + M_PI / 2);
if (angle >= -M_PI / 3)
color.b = 127 + 128 * sin(angle);
gfx.setPixel(x, y, color);
Download source code
Download executable for Windows
And this is what I get:
float angle = z.arg();
angle = angle + M_PI;
angle = (int)(angle * 32 / M_PI);
angle = angle * M_PI / 32;
angle = angle - M_PI;
Download source code
Download executable for Windows
// draw the pixel
Color color;
float angle = z.arg();
color.r = 127 * (1 + sin(angle));
color.g = 127 * (1 + sin(angle + 2 * M_PI / 3));
color.b = 127 * (1 + sin(angle + 4 * M_PI / 3));
gfx.setPixel(x, y, color);
Download source code
Download executable for Windows
And this is what I get:
f(z) = z2 - 1 ; a = 1 + i
// the function
Complex f(Complex z)
{
// z^2 - 1
return (z * z) - Complex(1, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 2z
return z * 2;
}
[...]
Complex a(1, 1);
Download source code
Download executable for Windows
f(z) = z3 - 1 ; a = 2
// the function
Complex f(Complex z)
{
// z^3 - 1
return (z * z * z) - Complex(1, 0);
}
// derivative of the function
Complex df(Complex z)
{
// 3z^2
return (z * z) * 3;
}
[...]
Complex a(2, 0);
Download source code
Download executable for Windows
The strange case of f(z) = sin(z)
// the function
Complex f(Complex z)
{
// sin(z)
return Complex::Sin(z);
}
// derivative of the function
Complex df(Complex z)
{
// cos(z)
return Complex::Cos(z);
}
But this function has an infinite number of roots of the form n * π where n is an integer.
// are we near a root ?
Complex delta = z / Complex(M_PI, 0);
Then we compare this result with a complex created by taking the integer part of it:
Complex delta2 = delta - Complex((int)delta.re, 0);
double distance = delta2.squareMag();
if (distance < TOLERANCE)
{
Finally we can choose a color depending of this integer which is n:
found = true;
float n = (int)delta.re * 0.072345 * M_PI;
color.r = 127 * (1 + sin(n));
color.g = 127 * (1 + sin(n + 2 * M_PI / 3));
color.b = 127 * (1 + sin(n + 4 * M_PI / 3));
Download source code
Download executable for Windows
And after adding our shading coefficient we get this nice image:
The secant fractal
zn+1 = zn - f(zn) * (zn - zn-1) / (f(zn) - f(zn-1))
Here we don't need the derivative of the function but we need to use some tricks because the formula uses zn-1.
// convert the pixel pos to a complex number
Complex z[3];
for (int i = 0; i < 3; i++)
{
z[i].re = (x - centerX) * zoom - 0.75 + (2-i) * TOLERANCE;
z[i].im = (y - centerY) * zoom;
}
Notice that we add a small value to the real part because the method will not work if zn-1 is equal to zn.
z[2] = z[1] - f(z[1]) * (z[1] - z[0]) / (f(z[1]) - f(z[0]));
z[0] = z[1];
z[1] = z[2];
Download source code
Download executable for Windows
And here is the image we get:
The Halley method
zn+1 = zn - 2 * f(zn) * f'(zn) / (2 * [f'(zn)]2 - f(zn) * f''(zn))
It's a cubic method that uses the second derivative of the function.
// the function
Complex f(Complex z)
{
// z^3 - 1
static Complex one(1, 0);
return z * z * z - one;
}
// derivative of the function
Complex df(Complex z)
{
// 3 * z^2
return (z * z) * 3;
}
// second derivative of the function
Complex ddf(Complex z)
{
// 6 * z
return z * 6;
}
And the formula looks like that:
Complex fz = f(z);
Complex dz = df(z);
Complex ddz = ddf(z);
z = z - fz * dz * 2 / (dz * dz * 2 - fz * ddz);
Download source code
Download executable for Windows
The result is a bit more boring than the classic Newton fractal:
The Householder method
zn+1 = zn - (1 + h) * f(zn) / f'(zn)
Where
h = f(zn) * f''(zn) / (2 * [f'(zn)]2)
The formula is implemented like this:
Complex fz = f(z);
Complex dz = df(z);
Complex ddz = ddf(z);
Complex h = (fz * ddz) / (dz * dz * 2);
z = z - (Complex(1, 0) + h) * fz / dz;
Download source code
Download executable for Windows
And the result is a little bit more interesting:
The Nova fractal
zn+1 = zn - a * f(zn) / f'(zn) + c
The iteration will now look like this:
// iterate
int iter;
Complex a(1, 0);
Complex c(0.3, 0.3);
for (iter = 0; iter < MAX_ITERATIONS; iter++)
{
Complex oldZ = z;
z = z - a * f(z) / df(z) + c;
Now, as before we can't rely on the roots of the original function, so we will use the same argument coloring
Complex delta = z - oldZ;
double distance = delta.squareMag();
if (distance < TOLERANCE)
break;
}
Download source code
Download executable for Windows
And here is an example with a = 1 and c = 0.3 + 0.3i
The "other" Nova fractal
// iterate
int iter;
Complex a(1, 0);
Complex c = z;
z = Complex(-1, 0);
for (iter = 0; iter < MAX_ITERATIONS; iter++)
{
Complex oldZ = z;
z = z - a * f(z) / df(z) + c;
Download source code
Download executable for Windows
And this is what we get with a = 1 and z0 = -1