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.
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$$
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)$.
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é.
É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
'''
Ajoutez ces imports en début de programme :
import matplotlib.image as mpimg import numpy as np
numpy, alias np, permet de manipuler une image sous forme d'une grillempimg permet 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 :
img,img dans un fichierIl reste à prévoir une exécution. Par exemple :
draw('julia.png', 0, 0, 800, 600, 0.01)
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 :
Et dans la fonction draw, on colorie le pixel avec la couleur int(255*score).
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")
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