Table des matières

Ensemble de Mandelbrot

Ensemble de Mandelbrot

Benoît Mandelbrot

Benoît Mandelbrot (1924 - 2010) est un mathématicien franco-polonais inventeur des fractales. L'ensemble qu'il a découvert, à la suite de son professeur Gaston Julia, est devenu un mème.

Les fractales sont un champ d'étude aux multiples applications. On peut citer la théorie du Chaos et la génération procédurale : Par exemple, dans les jeux vidéos, les fractales permettent de générer automatiquement des environnement riches, denses, comme des forêts de feuillus.

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.

Prenons un exemple : si $c = (1 ; 1)$ alors la suite $z_n$ prend les valeurs :

$z_0 = (0 ; 0)$ ; $z_1 = (1 ; 1)$ ; $z_2 = (1 ; 3)$ ; $z_3 = (-7 ; 7)$ ; $z_4 = (1 ; 99)$ ; …

On voit que le point $z_n$ s'éloigne de l'origine. On dira que cela diverge.

Autre exemple, si $c = (-1 ; 0)$. Alors :

$z_0 = (0 ; 0)$ ; $z_1 = (-1 ; 0)$ ; $z_2 = (0 ; 0)$ ; $z_3 = (-1 ; 0)$ ; $z_4 = (0 ; 0)$ ; …

On voit que dans ce cas, $z_n$ oscille entre deux valeurs et donc ne s'éloigne pas indéfiniment de l'origine. Dans ce cas, cela ne diverge pas.

L'ensemble de Mandelbrot est l'ensemble des valeurs de $c$ pour lesquelles la suite $z_n$ ne diverge pas.

Ces points sont ceux en noir sur la figure en haut.

Donc $c = (1 ; 1)$ n'est pas dans l'ensemble de Mandelbrot. $c = (-1 ; 0)$ lui est dans l'ensemble.

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é.

Implémentation

Un point est-il dans l'ensemble ?

Écrire la fonction suivante :

ITERATIONS = 50

def is_mandelbrot(cx:float, cy:float) -> bool:
    '''
    cx, cy: coordonnées du point c testé
    renvoie True si c est dans l'ensemble de Mandelbrot
    '''

Créer une image

Ajoutez ces imports en début de programme :

import matplotlib.image as mpimg
import numpy as np

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 :

  1. créer le tableau numpy img,
  2. 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
  3. sauvegarder le tableau img dans un fichier

Il reste à prévoir une exécution. Par exemple :

draw('mandelbrot.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 mandelbrot_score(cx:float, cy:float) -> float:
    '''
    cx, cy: coordonnées du point c 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).

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

Solutions dans d'autres langages