Table des matières
Ensembles de Julia
Gaston Julia (1893 - 1978) est un mathématicien français, spécialiste des fonctions d'une variable complexe. Ses résultats de 1917-1918 sur l'itération des fractions rationnelles ont été remis en lumière dans les années 1970 par Benoît Mandelbrot. Les ensembles de Julia et de Mandelbrot sont étroitement associés et font partie des ensembles fractales.
Vous avez peut-être remarqué l'étrange masque que porte Gaston Julia sur l'illustration. Il est une “Gueule cassée” de la Première Guerre Mondiale. Grièvement blessé au visage en 1915, à 22 ans, il devra porter ce masque toute sa vie.
Définition de l'ensemble
La définition de l'ensemble utilise les nombres complexes et le plan complexe. Mais on peut comprendre sans avoir besoin de savoir ce qu'est un nombre complexe. Je vais donc adopter ici une description qui reste compréhensible même sans connaître les nombres complexes.
J'écrirais les coordonnées des points $(x ; y)$ comme s'il s'agissait d'un nombre $z$ et j'écrirais donc $z = (x ; y )$ ce qui ne respecte pas les notations mathématiques mais est compréhensible. Et pour aller jusqu'au bout de l'abus de notation, je parlerai indifféremment de point et de coordonnées comme si on pouvait dire point = coordonnées, ce qui, là encore, n'est pas tout à fait rigoureux.
On se place dans le plan doté d'un repère orthonormé. Les points ont des coordonnées $(x ; y)$. Comme dit dans l'avertissement ci-dessus, je nomme $z$ à la fois le point et ses coordonnées ce qui me permet d'écrire $z = (x ; y )$.
On considère une suite de points $z_n$ définis par récurrence :
$$z_{n + 1} = z_n^2 + c \quad ; \quad z_0 = (0 ; 0)$$
Ce calcul se fait avec les nombres complexes mais pour ceux qui ne connaissent pas je l'explicite. avec $z_{n+1} = (x_{n+1} ; y_{n+1})$ : $$x_{n+1} = x_n^2 - y_n^2 + x_c$$ $$y_{n+1} = 2 \cdot x_n \cdot y_n + y_c$$
Suite divergente ?
Les valeurs de $z_n$ n'ont pas d'importance par elles-mêmes. On veut juste savoir si la suite $z_n$ diverge ou non. Autrement dit, on veut savoir si $(x_n ; y_n)$ finit par s'éloigner infiniment de l'origine.
Cela dépend du choix de $z_0$ et de $c$.
Mandelbrot et Julia sont en quelque sorte complémentaires : Pour l'ensemble de Mandelbrot, on fixe $z_0 = 0$ et on cherche les $c$ pour lesquels la suite ne diverge pas. Pour Julia c'est l'inverse : on fixe $c$ a une valeur et on cherche les $z_0$ pour lesquels la suite ne diverge pas.
Prenons un exemple : si $c = (1 ; 1)$ et $z_0 = (0,-2)$. Alors la suite $z_n$ prend les valeurs :
$z_0 = (0 ; -2)$ ; $z_1 = (-3 ; 1)$ ; $z_2 = (9 ; -5)$ ; $z_3 = (57 ; -89)$ ; …
On voit que le point $z_n$ s'éloigne de l'origine. On dira que cela diverge.
L'ensemble de Julia est l'ensemble des $z_0$ pour lesquels la suite $z_n$ ne diverge pas.
Cet ensemble dépend du choix de $c$. Il y a donc plusieurs ensembles de Julia.
Donc $z_0 = (0 ; -2)$ n'est pas dans l'ensemble de Julia pour $c = (1 ; 1)$.
Calcul pratique
Il est très difficile, pour un $c$ quelconque, de prévoir si la suite divergera ou non. Pour dessiner l'ensemble on se contente d'un critère simplifié.
- On fixe la valeur de $c$ pour laquelle on veut l'ensemble de Julia. Par exemple $c = (0,3\,;\,0,5)$.
- On sait d'abord que si $|z_n| = \sqrt{x_n^2 + y_n^2} \geq 2$ alors la suite diverge.
- On se fixe un nombre $N$, par exemple $N = 50$ ou $N = 500$.
- On choisit la valeur de $z_0$ à tester,
- on calcule les termes $z_n$ avec $0 \geq n \geq N$,
- si on trouve un $|z_n| \geq 2$, alors c'est terminé, cette valeur de $z_0$ n'est pas dans l'ensemble. On pourra la représenté en blanc.
- sinon, on considère que $z_0$ est est dans l'ensemble et on pourra le représenter en noir.
Implémentation
Un point est-il dans l'ensemble ?
Écrire la fonction suivante :
ITERATIONS = 50
def is_julia(cx:float, cy:float, z0x:float, z0y:float) -> bool:
'''
cx, cy: coordonnées du c choisi
z0x, z0y: coordonnées du z0 testé
renvoie True si z0 est dans l'ensemble de Julia associé à c
'''
Créer une image
Ajoutez ces imports en début de programme :
import matplotlib.image as mpimg import numpy as np
numpy, aliasnp, permet de manipuler une image sous forme d'une grillempimgpermet de sauvegarder l'image dans une fichier
Créer une image 800×600 en monochrome (niveaux de gris) toute blanche :
# (50,50) : dimensions (HAUTEUR, LARGEUR) # np.uint8: chaque pixel est un octet donc entre 0 et 255 # 255: niveau max -> blanc img = np.full((50, 50), 255, np.uint8)
Modifier le contenu d'un pixel :
# mettre en noir la ligne 35 et colonne 12 : img[35,12] = 0
Enfin, pour sauvegarder l'image :
mpimg.imsave('mandelbrot.png', img, cmap="gray")
Maintenant à vous, créez la fonction suivante :
def draw(filename:string, centerx:float, centery:float, largeur:int, hauteur:int, dot:float):
'''
filename: nom du fichier produit
centerx, centery: coordonnées du point qui sera au centre de l'image
largeur, hauteur: dimension de l'image, en pixels
dot: taille du pixel
produit un fichier image contenant l'ensemble de Mandelbrot
'''
Pour arriver au bout, il faut :
- créer le tableau numpy
img, - parcourir chaque pixel et pour chaque pixel :
- calculer les coordonnées x, y correspondantes,
- tester si ces coordonnées sont dans l'ensemble de Mandelbrot
- si oui, mettre le pixel à 0, sinon mettre le pixel à 255
- sauvegarder le tableau
imgdans un fichier
Il reste à prévoir une exécution. Par exemple :
draw('julia.png', 0, 0, 800, 600, 0.01)
Niveaux de gris
L'image obtenue est en noir et blanc, tout ou rien. Ce n'est pas très joli. Pour obtenir une image plus belle, on veut mettre les points en plus ou moins clair.
On écrit la fonction :
def julia_score(cx:float, cy:float, z0x:float, z0y:float) -> float:
'''
cx, cy: coordonnées du c choisi
z0x, z0y: coordonnées du z0 testé
renvoie un score entre 0 et 1
0 pour un point dans l'ensemble
1 pour un point qui diverge vite
un score intermédiaire pour les autres
'''
Pour obtenir le score, on fait comme précédemment :
- $z_0 = (0 ; 0)$
- calcul en boucle de $z_{n+1} = z_n^2 + c$
- si on trouve $z_{n+1}$ tel que $|z_{n+1}|\geq 2$, on renvoie $1 - \frac{n}{N}$
- si on fait les $N$ boucles sans jamais avoir $|z_{n+1}|\geq 2$, alors on renvoie $0$
Et dans la fonction draw, on colorie le pixel avec la couleur int(255*score).
Un peu de couleur
On aime avoir des images en couleurs.
C'est très simple : il suffit de modifier l'option cmap dans mpimg.imsave(filename, img, cmap="gray")
Il y a beaucoup de possibilités, on peut les trouver ici. Par exemple, vous pouvez essayer mpimg.imsave(filename, img, cmap="plasma")
Les complexes dans Python
Python prend nativement en charge les complexes : pour $z = 4 + 5i$, il suffira d'écrire z = 4 + 5j.
Attention, si c'est $z = 4 + i$, il faut écrire z = 4 + 1j pour permettre à Python de reconnaître qu'on parle d'un complexe.
On pourra ensuite faire tous les calculs désirés. En particulier, pour $|z|$ il suffit d'écrire abs(z).
>>> z = 2 + 3j >>> c = 1 + 1j >>> z**2 + c (-4+13j) >>> abs(z) 3.6055512754639896
