Fractales de Newton
A propos du code
Définition
xn+1 = xn - f(xn) / f'(xn)
Où f'(x) est la dérivée de la fonction f(x).
f(z) = z3 - 1
Mais on verra plus tard qu'on peut essayer avec d'autres fonctions.
f'(z) = 3z2
Et on doit connaitre ses racines:
z1 = 1
z2 = -0.5 + i * √3 / 2
z3 = -0.5 - i * √3 / 2
Sur la page anglaise de la fractale de Newton il y a un morceau de code pour la dessiner.
#define MAX_ITERATIONS 100
#define TOLERANCE 1e-6
Ensuite, on définit notre fonction et sa dérivée.
// 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;
}
Dans la fonction main on va définir quelques valeurs pour dire où la fenêtre sera centrée et sa taille dans le plan
static double zoom = 2.0 / (double)SCREEN_HEIGHT;
static double centerX = (double)SCREEN_WIDTH / 2.0;
static double centerY = (double)SCREEN_HEIGHT / 2.0;
On va avoir besoin des racines de la fonction:
// 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);
Chaque pixel prendra une couleur en fonction de la racine qu'on a atteinte dans la boucle d'itération.
// 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);
Maintenant on va dessiner la fractale. Alors on commence une boucle sur tous les pixels de l'écran:
// draw the fractal
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
for (int x = 0; x < SCREEN_WIDTH; x++)
{
On convertit les coordonnées du pixel en nombre complexe:
// convert the pixel pos to a complex number
Complex z;
z.re = (x - centerX) * zoom - 0.75;
z.im = (y - centerY) * zoom;
On va utiliser une variable pour stocker la racine qu'on a atteint. Par défaut elle est sur la dernière couleur de
// by default the pixel is black if the calculations fail
int color = 3;
On lance la boucle d'itération
// iterate
int iter;
for (iter = 0; iter < MAX_ITERATIONS; iter++)
{
Voilà la formule de Newton
z = z - f(z) / df(z);
On boucle sur chaque racine pour tester si on en a atteint une
// are we near a root ?
for (int i = 0; i < 3; i++)
{
Complex delta = z - roots[i];
double distance = delta.squareMag();
Si on a atteint une racine, on stocke son numéro et on sort de la boucle de test des racines.
if (distance < TOLERANCE)
{
color = i;
break;
}
}
Et on sort aussi de la boucle d'itération:
if (color != 3)
break;
}
Et finalement on dessine le pixel.
// draw the pixel
gfx.setPixel(x, y, palette[color]);
Comme le dessin peut prendre du temps, à la fin de chaque ligne, on va l'afficher et gérer les évènements pour
}
gfx.render();
sys.processEvents();
if (sys.isQuitRequested() == true)
break;
}
A la fin, on rajoute une boucle pour attendre que l'utilisateur ferme la fenêtre quand le dessin est terminé.
// wait until we quit
while (sys.isQuitRequested() == false)
{
sys.processEvents();
sys.wait(16);
}
gfx.quit();
return EXIT_SUCCESS;
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voila le résultat:
Améliorer les couleurs
// 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);
Télécharger le code source
Télécharger l'exécutable pour Windows
Je calcule un coefficient à partir du logarithme du nombre d'itérations.
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);
Télécharger le code source
Télécharger l'exécutable pour Windows
J'ai aussi changé les couleurs et le coefficient d'ombrage pour être plus proche de l'image d'origine et voila ce
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);
Télécharger le code source
Télécharger l'exécutable pour 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);
Télécharger le code source
Télécharger l'exécutable pour 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);
Télécharger le code source
Télécharger l'exécutable pour Windows
Variante de la formule
zn+1 = zn - a * f(zn) / f'(zn)
Avec cette formule les racines de la fonction ne correspondent plus.
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);
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voila ce que j'obtiens:
float angle = z.arg();
angle = angle + M_PI;
angle = (int)(angle * 32 / M_PI);
angle = angle * M_PI / 32;
angle = angle - M_PI;
Télécharger le code source
Télécharger l'exécutable pour 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);
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voilà ce que j'ai obtenu:
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);
Télécharger le code source
Télécharger l'exécutable pour 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);
Télécharger le code source
Télécharger l'exécutable pour Windows
L'étrange cas de 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);
}
Mais cette fonction a un nombre de racines infini de la forme n * π où n est un entier.
// are we near a root ?
Complex delta = z / Complex(M_PI, 0);
Ensuite on compare ce résultat avec un complexe créé en prenant sa partie entière:
Complex delta2 = delta - Complex((int)delta.re, 0);
double distance = delta2.squareMag();
if (distance < TOLERANCE)
{
Finalement on peut choisir une couleur qui dépend de cet entier, qui est 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));
Télécharger le code source
Télécharger l'exécutable pour Windows
Et après avoir ajouté notre coefficient d'ombrage on obtient cette belle image:
La fractale sécante
zn+1 = zn - f(zn) * (zn - zn-1) / (f(zn) - f(zn-1))
Ici on n'a pas besoin de la dérivée de la fonction mais on va devoir utiliser des astuces parce que la formule
// 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;
}
Remarquez qu'on ajoute une petite valeur à la partie réelle, car la méthode ne marche pas si zn-1 est égal à 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];
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voici l'image qu'on obtient:
La méthode de Halley
zn+1 = zn - 2 * f(zn) * f'(zn) / (2 * [f'(zn)]2 - f(zn) * f''(zn))
C'est une méthode cubique qui utilise la dérivée seconde de la fonction.
// 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;
}
Et la formule ressemble à ça:
Complex fz = f(z);
Complex dz = df(z);
Complex ddz = ddf(z);
z = z - fz * dz * 2 / (dz * dz * 2 - fz * ddz);
Télécharger le code source
Télécharger l'exécutable pour Windows
Le résultat est un peu moins passionnant que la fractale de Newton classique:
La méthode de Householder
zn+1 = zn - (1 + h) * f(zn) / f'(zn)
Où
h = f(zn) * f''(zn) / (2 * [f'(zn)]2)
La formule est implémentée comme ceci:
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;
Télécharger le code source
Télécharger l'exécutable pour Windows
Et le résultat est un peu plus intéressant:
La fractale Nova
zn+1 = zn - a * f(zn) / f'(zn) + c
L'itération ressemble maintenant à ça:
// 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;
Et comme avant on ne peut pas compter sur les racines de la fonction d'origine, alors on va utiliser la même
Complex delta = z - oldZ;
double distance = delta.squareMag();
if (distance < TOLERANCE)
break;
}
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voici un exemple avec a = 1 et c = 0.3 + 0.3i
L'autre fractale Nova
// 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;
Télécharger le code source
Télécharger l'exécutable pour Windows
Et voici ce qu'on obtient avec a = 1 et z0 = -1