TP 10 : Automates cellulaires#

Un automate cellulaire consiste en une grille (matrice) de cellules contenant chacune un état qui peut évoluer au cours du temps. L’état d’une cellule au temps \(t+1\) dépend de l’état au temps \(t\) des cellules adjacentes.

Rappels sur numpy#

import numpy as np

On peut créer une matrice de taille \(n\times p\) remplie de \(0\) avec np.zeros((n,p)) :

m = np.zeros((2, 4))
m
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.]])

On peut accéder et modifier l’élément sur la ligne \(i\), colonne \(j\) avec m[i][j] :

m[0][0] = 1 # modifie l'élément ligne 0, colonne 0 (en haut à gauche)
m
array([[1., 0., 0., 0.],
       [0., 0., 0., 0.]])

Exercice

Écrire une fonction identite telle que identite(n) renvoie une matrice identité de taille \(n\times n\).

identite(5)
array([[1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 1.]])

Si m est une matrice, len(m) est son nombre de lignes et len(m[0]) est son nombre de colonnes :

print(len(m)) # nombre de lignes
print(len(m[0])) # nombre de colonnes
2
4

On peut aussi utiliser m.shape pour obtenir les dimensions de la matrice (sous forme de tuple) :

m.shape # m est de dimension 2x4
(2, 4)

On peut parcourir une matrice avec deux boucles for (une boucle pour chaque indice) :

for i in range(len(m)): # parcourt les lignes
    for j in range(len(m[0])): # parcourt les colonnes
        m[i][j] = i + j # modifie la matrice
m
array([[0., 1., 2., 3.],
       [1., 2., 3., 4.]])

Exercice

Écrire une fonction trace pour calculer la trace d’une matrice, c’est-à-dire la somme des éléments sur la diagonale.

trace(identite(5))
5.0

Fonctions utilitaires#

Si m est une matrice, len(m) est son nombre de lignes et len(m[0]) est son nombre de colonnes :

Question

Écrire une fonction bernouilli(p) renvoyant \(1\) avec une probabilité \(p\) et \(0\) avec une probabilité \(1 - p\).
On utilisera np.random.rand() pour générer un nombre aléatoire entre 0 et 1

Question

Écrire une fonction compter(L, e) qui renvoie le nombre d’occurrences de l’élément e dans la liste L.

compter([1, 0, 1, 1, 0, 0, 1], 1)
4

Question

Écrire une fonction matrice_aleatoire(n, p) qui renvoie une matrice de taille \(n \times n\) dont chaque élément est égal à 1 avec une probabilité \(p\) et 0 sinon.
On utilisera np.zeros((n, n), dtype=int) pour créer une matrice de taille \(n \times n\) remplie de 0.

M = matrice_aleatoire(5, 0.2)
M
array([[0, 0, 1, 1, 1],
       [1, 0, 0, 1, 0],
       [0, 1, 1, 0, 0],
       [1, 0, 1, 1, 0],
       [0, 0, 0, 0, 0]])

Question

Écrire une fonction voisins(M, i, j) qui renvoie la liste des \(8\) voisins de la case (i, j) de la matrice M. Il s’agit donc des valeurs en position \((k, l)\)\(k \in \{i-1, i, i+1\}\), \(l \in \{j-1, j, j+1\}\) et \((k, l) \neq (i, j)\). On fera attention à ne pas dépasser de la matrice.

voisins(M, 3, 1)
[0, 1, 1, 1, 1, 0, 0, 0]

Jeu de la vie#

Le jeu de la vie a été inventé par le mathématicien John Conway en 1970, et peut être vu comme la modélisation d’une population de cellules biologiques.
À chaque étape, une cellule peut être en vie (\(1\)) ou morte (\(0\)), avec les règles suivantes :

  • Une cellule morte possédant exactement trois cellules voisines vivantes devient vivante (elle naît).

  • Une cellule vivante possédant deux ou trois cellules voisines vivantes le reste, sinon elle meurt.

Question

Écrire une fonction regle_jeu_vie(M, i, j) qui renvoie l’état de la cellule (i, j) à l’étape suivante. On pourra réutiliser les fonctions voisins et compter écrites précédemment.

print(regle_jeu_vie(M, 3, 1))
print(regle_jeu_vie(M, 2, 2))
0
0

Question

Écrire une fonction etape_jeu_vie(M) qui renvoie la matrice correspondant à l’étape suivante du jeu de la vie.
Attention : il ne faut pas modifier M, mais renvoyer une nouvelle matrice.

Question

Utiliser la fonction jeu_vie ci-dessous pour afficher l’évolution du jeu de la vie, en utilisant sa documentation.
Remarque : si vous avez des problèmes après avoir fermé la fenêtre graphique, cliquer sur le bouton “Arrêter l’exécution” (en haut à droite dans Pyzo).

import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

def jeu_vie(M, n):
    """Affiche les n premières étapes du jeu de la vie.
    M : matrice initiale
    etape_jeu_vie : fonction qui calcule l'étape suivante
    n : nombre d'étapes
    """
    fig = plt.figure()
    def update(i) :
        nonlocal M
        plt.imshow(M, interpolation='nearest', origin='lower', cmap="gray")
        plt.title(f"Itération {i}")
        if i != 0:
            M = etape_jeu_vie(M)
    plt.axis('off')
    anim = FuncAnimation(fig, update, frames=n, interval=300, repeat=False)
    plt.show()
    # return HTML(anim.to_jshtml())
jeu_vie(matrice_aleatoire(20, 0.5), 30)
../../../_images/automate_cellulaire_39_1.png

Feu de forêt#

Nous souhaitons ici modéliser un feu de forêt. Chaque case de la matrice correspondra à un arbre qui peut prendre l’une des valeurs suivantes :

  • \(0\) : arbre non brûlé.

  • \(-1\) : arbre complètement brûlé.

  • un entier \(t \in \mathbb{N}^*\) : arbre en train de brûler, où \(t\) est le nombre d’étapes restantes avant que l’arbre ne soit complètement brûlé.

Question

Écrire une fonction voisin_brule(M, i, j) qui renvoie True si la case (i, j) de la matrice M est adjacente à au moins une case contenant un arbre en train de brûler et False sinon.

À chaque étape :

  • Une case contenant un entier \(t > 1\) devient \(t-1\) (arbre en train de brûler).

  • Une case contenant \(1\) devient \(-1\) (arbre complètement brûlé).

  • Une case contenant \(0\) qui est adjacente à au moins une case contenant \(1\) devient \(3\) avec une probabilité \(p\) (un arbre qui prend feu brûle pendant \(3\) tours). Sinon, cette case reste à \(0\).

Question

Écrire une fonction regle_feu(M, i, j, p) qui renvoie l’état de la case (i, j) à l’étape suivante, où p est la probabilité ci-dessus.

Question

Écrire une fonction etape_feu(M, p) qui renvoie la matrice correspondant à l’étape suivante du feu de forêt. Attention : il ne faut pas modifier M, mais renvoyer une nouvelle matrice.

Question

Utiliser la fonction feu_foret ci-dessous pour afficher l’évolution d’un feu de forêt, en utilisant sa documentation. Tester avec différentes probabilités de propagation du feu, en changeant la position du premier arbre en feu…

def feu_foret(M, n, p):
    """Affiche les n premières étapes du jeu de la vie.
    M : matrice initiale
    etape_jeu_vie : fonction qui calcule l'étape suivante
    n : nombre d'étapes
    """
    fig = mpl.pyplot.figure()
    cmap = mpl.colors.ListedColormap(['black', 'green', 'red'])
    bounds = [-1,-0.5, 0.5, 1]
    norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
    def update (i) :
        nonlocal M
        mpl.pyplot.imshow(M, interpolation='nearest', origin='lower', cmap=cmap, norm=norm)
        mpl.pyplot.title(f"Itération {i}")
        if i != 0:
            M = etape_feu(M, p)
    mpl.pyplot.axis('off')
    anim = FuncAnimation(fig, update, frames=n, interval=300, repeat=False)
    plt.show()
    #return HTML(anim.to_jshtml())
M = np.zeros((20, 20), dtype=int)
M[5][5] = 3 # un arbre en feu initialement
feu_foret(M, 30, 0.15)
../../../_images/automate_cellulaire_50_1.png