Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog

Jeu de la vie épidémique en Python

4 Mai 2020 , Rédigé par Hugues MEUNIER

Les algorithmes du type "jeu de la vie" sont utilisés depuis bien longtemps dans de nombreux domaines. Ils permettent également de simuler la propagation d'un virus dans une population donnée et ils offrent des effets visuels intéressants au niveau de la représentation graphique des résultats. Ils permettent de rendre compte de l'évolution d'un modèle SIR(M) probabilistes.

Le programme ci-dessous a été construit avec les hypothèses suivantes :

- l'immunité acquise ou initiale est permanente

- une personne infectée infecte ses "voisins" avec un taux de contagion probaContag

- la maladie est létale à partir du 8ème jour avec une probabilité de décès de probaDec

- les états possibles sont Sains, Infectés, Immunisés (Remis) ou Morts

Si j'exécute le programme avec les paramètres suivants :

- probaContag = 0.055

- probaDec = 0,005

- NbJours infection = 14

j'obtiens ce genre de simulation (la couleur représente l'état : blanc=sain, rouge=infecté, vert=immunisé, noir=décédé).

Simulation vidéo 1

et la dynamique du modèle est la suivante (l'échelle est logarithmique!) :

 

Cette simulation représente une épidémie très contagieuse; elle se propage très vite et la totalité de la population considérée devient soit immunisée ou soit décédée au bout de 120 jours.

 

Maintenant si j'exécute le programme avec les paramètres suivants :

- probaContag = 0.015

- probaDec = 0,005

- NbJours infection = 14

j'obtiens la simulation suivante:

Simulation vidéo 2

et la dynamique du modèle est la suivante (l'échelle est logarithmique!) :

Le programme python est le suivant :

#
# Simulation de la propagation d'une épidémie
# via un algorithme probabiliste du type jeu de la vie
# Hugues MEUNIER
# avril 2020
#

import numpy as np
from tkinter import Tk, Canvas, Button, RIGHT, LEFT
from random import random
import time
from matplotlib import pyplot as plt
from matplotlib.figure import Figure


# Par défaut le monde est créé en 2 dimensions et de taille NbL*NbC
NbL = 100  # hauteur du tableau
NbC = 100  # largeur du tableau
# NbJours est la durée de l'inefection avant d'être immunisé (l'immunité est supposée acquise)
NbJours = 14 #Nb de jours d'infection (incubation+ maladie) avant d'être remis
a = 7    # taille d'une cellule
NbBoucle = 200 #Nombre de jours pour la simulation

# Définitions des matrices de la simulation
cell = np.zeros((NbL,NbC),dtype=int)
etat = np.zeros((NbL,NbC),dtype=int)
NbGen = 0 # le jour courant

# Définition du dictionnaire des états possibles SAIN, MALADEx, IMMUN, DECES
# il y a x états MALADEx fonction du NbJours pendant lesquels la personne est infectée
state = {"SAIN":0}
bou = 1
# Boucle pour les NbJours d'infection
while bou <= NbJours:
    cle = 'MALADE'+str(bou)
    state[cle] = bou
    bou+=1
state["IMMUN"] = NbJours+1
state["DECES"] = NbJours+2

# Une couleur égale un état
CoulSain = "white"
CoulImmun = "green"
CoulMal = "red"
CoulDec = "black"

# Paramètre de la simulation
DensiteImmun = 0.0    # densité d'individus immunisés dans la population au temps zéro (vaccination ...)
ProbaContag = 0.015     # taux de contagion 0.055 signifie une probabilité de 5,5% de contaminer une personne à chaque contact
ProbaDec = 0.005        # taux de mortalité 0.005 signifie 0,5% de probabilité de décès par jour malade

#Initialisation des tableaux permettant de stocker les statistiques de l'épidémie
D = np.zeros(NbBoucle) # Tableau des personnes décédées
I = np.zeros(NbBoucle)# Tableau des personnes infectées ou malades
R = np.zeros(NbBoucle) #Tableau des personnes rétablies et immunisées
S = np.zeros(NbBoucle) # Tableau des personnes saines
t = np.linspace(0, NbBoucle, NbBoucle) # Tableau qui contient les jours

# Conditions initiales pour le démarrage de la simulation
D[0] = 0
I[0] = 0
R[0] = NbL*NbC*DensiteImmun
S[0] = NbL*NbC -D[0]-I[0]-R[0] 


# Fonction appelée à chaque itération (jour) pour calculer l'état du monde
def iterer(p, tau):
    appliquer_regles(p, tau)
    dessiner()

# Fonction d'initialisation
def initialiser_monde(p):
    # on répartit aléatoirement les personnes immunisées dans le monde
    etat[0:NbL,0:NbC] = state["SAIN"]    
    for x in range(NbL):
        for y in range(NbC):
            if random() < p:
                etat[x,y] = state["IMMUN"]
                R[NbGen]+=1
    # création de la grille d'affichage
    for x in range(NbL):
        for y in range(NbC):
            if etat[x,y]==state["SAIN"]:
                coul = CoulSain
            if etat[x,y]==state["IMMUN"]:
                coul = CoulImmun
            cell[x,y] = canvas.create_rectangle((x*a, y*a, (x+1)*a, \
                         (y+1)*a), outline="gray", fill=coul)
    S[0] = NbL*NbC -D[0]-I[0]-R[0]
    canvas.itemconfig(canvas_txt_NbS, text="Nb sains: "+str(S[NbGen]))
    canvas.itemconfig(canvas_txt_NbI, text="Nb infectés: "+str(I[NbGen]))
    canvas.itemconfig(canvas_txt_NbR, text="Nb remis: "+str(R[NbGen]))
    canvas.itemconfig(canvas_txt_NbD, text="Nb décès: "+str(D[NbGen]))
    

# Fonction qui calcule l'état du monde à chaque itération en fonction des règles du jeu suivantes:
# Si un individu est infecté, il infecte tous ses voisins au sens de Von Neumann avec une probabilité égale au taux de contagion
# Si un individu est au NbJours de l'infection soit il décède avec la probabilité égale au taux de décès ou soit il est immunisé
# L'infection est létale uniquement à partir du 8eme jour de l'infection
def appliquer_regles(p,tau):
    global etat
    temp = etat.copy()  # sauvegarde de l'état courant
    for x in range(NbL):
        for y in range(NbC):
            if etat[x,y] >=1 and etat[x,y]<=NbJours:
                if etat[(x-1)%NbL,(y+1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[(x-1)%NbL,(y+1)%NbC] = state["MALADE1"]                                
                if etat[x,(y+1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[x,(y+1)%NbC] = state["MALADE1"] 
                if etat[(x+1)%NbL,(y+1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[(x+1)%NbL,(y+1)%NbC] = state["MALADE1"] 
                if etat[(x-1)%NbL,y] == state["SAIN"]:
                    if random() < p:
                        temp[(x-1)%NbL,y] = state["MALADE1"] 
                if etat[(x+1)%NbL,y] == state["SAIN"]:
                    if random() < p:
                        temp[(x+1)%NbL,y] = state["MALADE1"] 
                if etat[(x-1)%NbL,(y-1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[(x-1)%NbL,(y-1)%NbC] = state["MALADE1"] 
                if etat[x,(y-1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[x,(y-1)%NbC] = state["MALADE1"] 
                if etat[(x+1)%NbL,(y-1)%NbC] == state["SAIN"]:
                    if random() < p:
                        temp[(x+1)%NbL,(y-1)%NbC] = state["MALADE1"] 
                if etat[x,y] == NbJours:
                    if random() < tau:  
                        temp[x,y] = state["DECES"]
                    else:
                        temp[x,y] = state["IMMUN"]
                else:
                    if etat[x,y] >= 7:
                        if random() < tau:  
                            temp[x,y] = state["DECES"]
                        else:
                            temp[x,y] = etat[x,y]+1
                    else:
                        temp[x,y] = etat[x,y]+1            
                
    etat = temp.copy()  # maj de l'état courant


# Dessiner toutes les cellules
def dessiner():
    for x in range(NbL):
        for y in range(NbC):
            if etat[x,y]==state["SAIN"]:
                couleur = CoulSain
            if etat[x,y]==state["IMMUN"]:
                couleur = CoulImmun
            if etat[x,y]==state["DECES"]:
                couleur = CoulDec
            if etat[x,y]>=1 and etat[x,y]<=NbJours:
                couleur = CoulMal
            canvas.itemconfig(cell[x][y], fill=couleur)
            
# Animation 
def pasapas():
    global NbGen
    i = 0
    while i < NbBoucle-1:
        NbGen += 1
        canvas.itemconfig(canvas_txt_NbJours, text="NbJours: "+str(NbGen))
        iterer(ProbaContag, ProbaDec)
        Compte()
        canvas.itemconfig(canvas_txt_NbS, text="Nb sains: "+str(S[NbGen]))
        canvas.itemconfig(canvas_txt_NbI, text="Nb infectés: "+str(I[NbGen]))
        canvas.itemconfig(canvas_txt_NbR, text="Nb remis: "+str(R[NbGen]))
        canvas.itemconfig(canvas_txt_NbD, text="Nb décès: "+str(D[NbGen]))
        if I[NbGen] == 0:
            print("Epidémie terminée!")
            Sortie()
            break
        #time.sleep(1)
        i+=1
        canvas.update()
    Sortie()

# Fonction de traitement du clic gauche de la souris
def  Infecter(event):
    x, y = event.x//a, event.y//a
    # on ne peut pas infecter un individu immunisé
    if etat[x,y] != state["IMMUN"]:
        etat[x,y] = state["MALADE1"]
        I[0]+=1 #on ajoute une personne infectée à chaque clic gauche de la souris
        S[0] = NbL*NbC -D[0]-I[0]-R[0]
        canvas.itemconfig(cell[x][y], fill=CoulMal) #on dessine en rouge la cellule infectée
        canvas.itemconfig(canvas_txt_NbS, text="Nb sains: "+str(S[NbGen]))
        canvas.itemconfig(canvas_txt_NbI, text="Nb infectés: "+str(I[NbGen]))
        canvas.itemconfig(canvas_txt_NbR, text="Nb remis: "+str(R[NbGen]))
        canvas.itemconfig(canvas_txt_NbD, text="Nb décès: "+str(D[NbGen]))

# Fonction de comptage des polupations saines, infectées, décèdées et remises
def Compte():
    nbS = 0
    nbI = 0
    nbR = 0
    nbD = 0
    x=0
    y=0
    while x < NbL:
        while y < NbC:
            if etat[x,y] == state["IMMUN"]:
                nbR+=1
            if etat[x,y] == state["DECES"]:
                nbD+=1
            if etat[x,y] == state["SAIN"]:
                nbS+=1
            if etat[x,y]>=1 and etat[x,y]<=NbJours:
                nbI+=1
            y+=1
        y=0
        x+=1
    I[NbGen] = nbI
    R[NbGen] = nbR
    D[NbGen] = nbD
    S[NbGen] = nbS

# Fonction de sortie de la simulation avec affichage de la courbe d'évolution des populations S, I, R, D
def Sortie():
    fenetre.destroy()
    # Trace les courbes
    fig = plt.figure(facecolor='w')
    fig_size = plt.rcParams["figure.figsize"]
    print("Sortie...")
    fig_size[0] = 36
    fig_size[1] = 24
    plt.rcParams["figure.figsize"] = fig_size
    I2 = np.trim_zeros(I,trim = 'b')
    NbElem = np.size(I2)
    
    
    plt.title('Evolution de l\'épidémie')
    plt.plot(t[0:NbElem], S[0:NbElem], color='blue', label='Sains')
    plt.plot(t[0:NbElem], I[0:NbElem], color='red', label='Infectés')
    plt.plot(t[0:NbElem], R[0:NbElem], color='green', label='Remis')
    plt.plot(t[0:NbElem], D[0:NbElem], color='black', label='Décédés')
    #plt.plot(t, R, color='green', label='Remis')
    plt.xlabel('Nb de jours')
    plt.ylabel('Nb de personnes (log)')
    leg = plt.legend();
    plt.grid()
    plt.yscale('log')
    plt.show()
    # on ne profite pour sauver le graphe au format png
    fig.savefig("simul-ep.png", format='png', bbox_inches='tight')

# Définition de l'interface graphique
fenetre = Tk()
fenetre.title("Le jeu de la vie épidémique")
canvas = Canvas(fenetre, width=a*NbC+150, height=a*NbL+1, highlightthickness=0)
fenetre.wm_attributes("-topmost", True)
# Allocation de la fonction Infecter sur click gauche
canvas.bind("<Button-1>", Infecter)
canvas.pack()

# Définition des boutons de commande
bou1 = Button(fenetre,text='Sortie', width=8, command=Sortie)
bou1.pack(side=RIGHT)
bou2 = Button(fenetre, text='Go!', width=8, command=pasapas)
bou2.pack(side=LEFT)

#Définition des zones de texte pour afficher les compteurs pendant la simulation
canvas_txt_NbJours = canvas.create_text(NbC*a+20,20, anchor="nw")
canvas.itemconfig(canvas_txt_NbJours, text="NbJours: "+str(NbGen))
canvas_txt_NbS = canvas.create_text(NbC*a+20,40, anchor="nw")
canvas.itemconfig(canvas_txt_NbS, text="Nb sains: "+str(S[NbGen]))
canvas_txt_NbI = canvas.create_text(NbC*a+20,60, anchor="nw")
canvas.itemconfig(canvas_txt_NbI, text="Nb infectés: "+str(I[NbGen]))
canvas_txt_NbR = canvas.create_text(NbC*a+20,80, anchor="nw")
canvas.itemconfig(canvas_txt_NbR, text="Nb remis: "+str(R[NbGen]))
canvas_txt_NbD = canvas.create_text(NbC*a+20,100, anchor="nw")
canvas.itemconfig(canvas_txt_NbD, text="Nb décès: "+str(D[NbGen]))

# lancement de l'automate
initialiser_monde(DensiteImmun)
fenetre.mainloop()

Partager cet article

Repost0
Pour être informé des derniers articles, inscrivez vous :

Commenter cet article