Code source de ensae_teaching_cs.special.voisinage_evolution

# -*- coding: utf-8 -*-
"""
@file
@brief Implémente une simulation d'évolution des catégories de population
selon un modèle de Schelling.
"""
import random
import copy
import os
from pyquickhelper.loghelper import noLOG
from ..helpers.pygame_helper import wait_event, empty_main_loop


[docs]def round(r, g, b): """ arrondit chaque couleur """ return (int(r), int(g), int(b))
[docs]class Ville: """ Définit une ville qui va évoluer par la suite. @param colors couleurs vives : simulation sans tenir compte de riches ou pauvres, seulement regroupement @param colors_grade simulation en tenant compte des riches, du plus foncé au plus clair (riches) """ colors = {-1: (0, 0, 0), 0: (255, 0, 0), 1: (0, 255, 0), 2: (0, 0, 255), 3: (255, 255, 0), 4: (255, 0, 255), 5: (0, 255, 255)} colors_grade = {-1: (0, 0, 0), 0: round(131.28918999850276, 137.49288815690971, 51.799520886360227), 1: round(151.28918999850276, 147.49288815690971, 71.799520886360227), 2: round(191.42448385755856, 191.27629208812527, 57.413606761812389), 3: round(190.99311386065693, 133.49749594932979, 41.781926646045072), 4: round(167.25849848112253, 76.347509523120692, 41.289551087323403), 5: round(196.76664713923063, 39.476078890841634, 31.506444053895724) }
[docs] def __init__(self, cote=100, group=3, taille=3, riche=False, th2=1.2, renouvellement=0.15, delay=1): """ constructeur @param cote côté du carré utilisé pour la simulation @param group nombre de catégories de gens @param taille chaque individu regarde ses voisins à *+/-* taille près @param riche simulation avec riche ou non @param th2 le voisin le plus pauvre peut être contaminé, si la différence de classes est importante (`cl1 > cl2 * th2`) @param renouvellement à chaque itération, une certaine proportion des pâtés sont mis à jour, cette proportion correspond au renouvellement @param delay la simulation prend en compte la ville lors des "delay" dernières itérations On tire au hasard la classe d'un pâté de maison dans un disque de rayon cote. """ if cote is None: pass else: self.mat = [[random.randint(0, group - 1) for i in range(0, cote)] for j in range(0, cote)] self.group = group self.taille = taille self.past = [] self.th2 = th2 self.riche = riche self.delay = delay self.renouvellement = renouvellement c = len(self.mat) / 2 R = c ** 2 / 4 for i in range(0, len(self.mat)): for j in range(0, len(self.mat[0])): d = (i - c) ** 2 + (j - c) ** 2 if d > R: self.mat[i][j] = -1
[docs] def _voisinage(self, i, j, mat): """ calcul de la répartition du voisiage @param i i,j coordonnées @param j @param mat matrice @return dictionnaire { classe:nombre } """ d = {} x1 = max(0, i - self.taille) y1 = max(0, j - self.taille) x2 = min(len(self.mat), i + self.taille + 1) y2 = min(len(self.mat), j + self.taille + 1) for ii in range(x1, x2): for jj in range(y1, y2): c = mat[ii][jj] if c not in d: d[c] = 0 d[c] += 1 return d
[docs] def evolution(self): """ évolution d'une itération à l'autre @return nb1,nb2 """ keep = copy.deepcopy(self.mat) self.past.append(keep) if len(self.past) > self.delay: del self.past[:len(self.past) - self.delay] # def fff(x, c): # if c not in x: # return 0 # elif x[c] >= sum(x.values()) * self.th: # return 1 # else: # return 0 # on renouvelle une certaine proportion de pâtés (renouvellement) # tiré au hasard nb1, nb2 = 0, 0 for n in range(0, int(len(self.mat) ** 2 * self.renouvellement)): # on tire deux voisins au hasard i = random.randint(0, len(self.mat) - 1) j = random.randint(0, len(self.mat) - 1) k = i + random.randint(-1, 1) l_ = j + random.randint(-1, 1) if k == i and l_ == j: continue x1 = max(0, k) y1 = max(0, l_) x2 = min(len(self.mat) - 1, k) y2 = min(len(self.mat) - 1, l_) if x1 != x2 or y1 != y2: continue # calcul des deux voisinages v1 = self._voisinage(i, j, self.mat) v2 = self._voisinage(k, l_, self.mat) c = self.mat[i][j] d = self.mat[k][l_] # c,d : leurs catégorie if c >= 0 and d >= 0: # s'ils sont tous les deux habités if v1.get(c, 0) < v2.get(c, 0) and v1.get(d, 0) > v2.get(d, 0): # premier cas: si l'un voisin a plus de voisins qui ressemblent à l'autre # et réciproquement, ils échangent self.mat[k][l_] = c self.mat[i][j] = d nb1 += 1 elif v1.get(c, 0) > v2.get(d, 0) * self.th2 and (not self.riche or c > d): # deuxième cas : cas riche, le voisin le plus pauvre peut-être contaminé # si la différence est importante self.mat[k][l_] = c nb2 += 1 elif c == -1: # celui qui n'est pas habité prend la couleur de l'autre self.mat[i][j] = d elif d == -1: # celui qui n'est pas habité prend la couleur de l'autre self.mat[k][l_] = c return nb1, nb2
[docs] def count(self): """ @return la population """ d = {} for line in self.mat: for c in line: if c not in d: d[c] = 1 else: d[c] += 1 return d
[docs]class VilleImage(Ville): """ Définit une ville à partir d'une image (donc non aléatoire). """
[docs] def __init__(self, image, cote=100, group=3, taille=3, riche=False, th2=1.2, renouvellement=0.15, delay=1): """ constructeur @param image nom d'une image pour définir l'initialisation @param cote cote du carré utilisé pour la simulation @param group nombre de catégories de gens @param taille chaque individu regarde ses voisins à +- taille près @param riche simulation avec riche ou non @param th2 le voisin le plus pauvre peut-être contaminé, si la différence de classes est importante (cl1 > cl2 * th2) @param renouvellement à chaque itération, une certaine proportion des pâtés sont mis à jour, cette proportion correspond à renouvellement @param delay la simulation prend en compte la ville lors des "delay" dernières itérations On tire au hasard la classe d'un pâté de maison dans un disque de rayon cote. """ Ville.__init__(self, cote, group, taille, riche, th2, renouvellement, delay) self._initialisation(image)
[docs] def _initialisation(self, im): for i in range(0, len(self.mat)): for j in range(0, len(self.mat[0])): p = im.get_at((i, j)) mins = 1e6 best = None for k, v in Ville.colors_grade.items(): s = 0 for z in [0, 1, 2]: s += (v[z] - p[z]) ** 2 s = s ** 0.5 if s < mins: mins = s best = k self.mat[i][j] = best
[docs]def display(self, screen, x, pygame): """ affichage @param screen écran @param x dimension d'un pâté de maison """ screen.fill((0, 0, 0)) if self.riche: colors = Ville.colors_grade else: colors = Ville.colors for i in range(0, len(self.mat)): for j in range(0, len(self.mat[i])): c = colors[self.mat[i][j]] pygame.draw.rect(screen, c, pygame.Rect(i * x, j * x, x, x))
[docs]def pygame_simulation(pygame, first_click=False, folder=None, x=6, nb=100, group=6, max_iter=150, th2=1.75, image=None, flags=0, fLOG=noLOG): """ Simulation graphique. Illuste la résolution du puzzle @param pygame module pygame @param first_click attend la pression d'un clic de souris avant de commencer @param folder répertoire où stocker les images de la simulation @param size taille de l'écran @param delay delay between two tries @param x pour l'affichage, taille d'un pâté de maison à l'écran @param group ... @param nb taille du carré de la simulation en nombre de pâtés de maisons @param th2 ... @param max_iter nombre d'itérations @param image définition de la ville @param flags see `pygame.display.set_mode <https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode>`_ @param fLOG logging function @return @see cl Ville La simulation ressemble à ceci : .. raw:: html <video autoplay="" controls="" loop="" height="500"> <source src="http://www.xavierdupre.fr/enseignement/complements/voisinage.mp4" type="video/mp4" /> </video> Pour lancer la simulation:: from ensae_teaching_cs.special.voisinage_evolution import pygame_simulation import pygame pygame_simulation(pygame) Voir :ref:`l-simulation_voisinage`. """ if image is None: this = os.path.dirname(__file__) image = os.path.join(this, "paris_today.png") image = pygame.image.load(image) image = pygame.transform.scale(image, (100, 100)) pygame.init() size = nb * x, nb * x screen = pygame.display.set_mode(size, flags) ville = VilleImage(image, nb, group, th2=th2, riche=True) if first_click and pygame is not None: wait_event(pygame) if pygame is not None: display(ville, screen, x, pygame) pygame.display.flip() images = [] if folder is not None: images.append(screen.copy()) fLOG(ville.count()) for i in range(0, max_iter): nb = ville.evolution() fLOG("iteration ", i, " ch ", nb) if pygame is not None: if folder is not None: images.append(screen.copy()) display(ville, screen, x, pygame) pygame.display.flip() empty_main_loop(pygame) fLOG(ville.count()) if first_click and pygame is not None: wait_event(pygame) if folder is not None and pygame is not None: images.append(screen.copy()) if folder is not None: fLOG("saving images") for it, screen in enumerate(images): if it % 10 == 0: fLOG("saving image:", it) image = os.path.join(folder, "image_%04d.png" % it) pygame.image.save(screen, image) return ville