Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Implémente une simulation d'évolution des catégories de population
5selon un modèle de Schelling.
6"""
7import random
8import copy
9import os
10from pyquickhelper.loghelper import noLOG
11from ..helpers.pygame_helper import wait_event, empty_main_loop
14def round(r, g, b):
15 """
16 arrondit chaque couleur
17 """
18 return (int(r), int(g), int(b))
21class Ville:
22 """
23 Définit une ville qui va évoluer par la suite.
25 @param colors couleurs vives : simulation sans tenir compte de riches ou pauvres,
26 seulement regroupement
27 @param colors_grade simulation en tenant compte des riches, du plus foncé
28 au plus clair (riches)
29 """
30 colors = {-1: (0, 0, 0), 0: (255, 0, 0), 1: (0, 255, 0), 2: (0, 0, 255),
31 3: (255, 255, 0), 4: (255, 0, 255), 5: (0, 255, 255)}
33 colors_grade = {-1: (0, 0, 0), 0: round(131.28918999850276, 137.49288815690971, 51.799520886360227),
34 1: round(151.28918999850276, 147.49288815690971, 71.799520886360227),
35 2: round(191.42448385755856, 191.27629208812527, 57.413606761812389),
36 3: round(190.99311386065693, 133.49749594932979, 41.781926646045072),
37 4: round(167.25849848112253, 76.347509523120692, 41.289551087323403),
38 5: round(196.76664713923063, 39.476078890841634, 31.506444053895724)
39 }
41 def __init__(self, cote=100, group=3, taille=3, riche=False, th2=1.2,
42 renouvellement=0.15, delay=1):
43 """
44 constructeur
46 @param cote côté du carré utilisé pour la simulation
47 @param group nombre de catégories de gens
48 @param taille chaque individu regarde ses voisins à *+/-* taille près
49 @param riche simulation avec riche ou non
50 @param th2 le voisin le plus pauvre peut être contaminé,
51 si la différence de classes est importante (`cl1 > cl2 * th2`)
52 @param renouvellement à chaque itération, une certaine proportion des pâtés sont mis à jour,
53 cette proportion correspond au renouvellement
54 @param delay la simulation prend en compte la ville lors des "delay" dernières itérations
56 On tire au hasard la classe d'un pâté de maison dans un disque de rayon cote.
57 """
58 if cote is None:
59 pass
60 else:
61 self.mat = [[random.randint(0, group - 1)
62 for i in range(0, cote)] for j in range(0, cote)]
63 self.group = group
64 self.taille = taille
65 self.past = []
66 self.th2 = th2
67 self.riche = riche
68 self.delay = delay
69 self.renouvellement = renouvellement
70 c = len(self.mat) / 2
71 R = c ** 2 / 4
72 for i in range(0, len(self.mat)):
73 for j in range(0, len(self.mat[0])):
74 d = (i - c) ** 2 + (j - c) ** 2
75 if d > R:
76 self.mat[i][j] = -1
78 def _voisinage(self, i, j, mat):
79 """
80 calcul de la répartition du voisiage
82 @param i i,j coordonnées
83 @param j
84 @param mat matrice
85 @return dictionnaire { classe:nombre }
86 """
87 d = {}
88 x1 = max(0, i - self.taille)
89 y1 = max(0, j - self.taille)
90 x2 = min(len(self.mat), i + self.taille + 1)
91 y2 = min(len(self.mat), j + self.taille + 1)
92 for ii in range(x1, x2):
93 for jj in range(y1, y2):
94 c = mat[ii][jj]
95 if c not in d:
96 d[c] = 0
97 d[c] += 1
98 return d
100 def evolution(self):
101 """
102 évolution d'une itération à l'autre
104 @return nb1,nb2
105 """
107 keep = copy.deepcopy(self.mat)
108 self.past.append(keep)
109 if len(self.past) > self.delay:
110 del self.past[:len(self.past) - self.delay]
112 # def fff(x, c):
113 # if c not in x:
114 # return 0
115 # elif x[c] >= sum(x.values()) * self.th:
116 # return 1
117 # else:
118 # return 0
120 # on renouvelle une certaine proportion de pâtés (renouvellement)
121 # tiré au hasard
122 nb1, nb2 = 0, 0
123 for n in range(0, int(len(self.mat) ** 2 * self.renouvellement)):
125 # on tire deux voisins au hasard
126 i = random.randint(0, len(self.mat) - 1)
127 j = random.randint(0, len(self.mat) - 1)
128 k = i + random.randint(-1, 1)
129 l_ = j + random.randint(-1, 1)
130 if k == i and l_ == j:
131 continue
132 x1 = max(0, k)
133 y1 = max(0, l_)
134 x2 = min(len(self.mat) - 1, k)
135 y2 = min(len(self.mat) - 1, l_)
136 if x1 != x2 or y1 != y2:
137 continue
139 # calcul des deux voisinages
140 v1 = self._voisinage(i, j, self.mat)
141 v2 = self._voisinage(k, l_, self.mat)
142 c = self.mat[i][j]
143 d = self.mat[k][l_]
145 # c,d : leurs catégorie
147 if c >= 0 and d >= 0:
148 # s'ils sont tous les deux habités
149 if v1.get(c, 0) < v2.get(c, 0) and v1.get(d, 0) > v2.get(d, 0):
150 # premier cas: si l'un voisin a plus de voisins qui ressemblent à l'autre
151 # et réciproquement, ils échangent
152 self.mat[k][l_] = c
153 self.mat[i][j] = d
154 nb1 += 1
155 elif v1.get(c, 0) > v2.get(d, 0) * self.th2 and (not self.riche or c > d):
156 # deuxième cas : cas riche, le voisin le plus pauvre peut-être contaminé
157 # si la différence est importante
158 self.mat[k][l_] = c
159 nb2 += 1
160 elif c == -1:
161 # celui qui n'est pas habité prend la couleur de l'autre
162 self.mat[i][j] = d
163 elif d == -1:
164 # celui qui n'est pas habité prend la couleur de l'autre
165 self.mat[k][l_] = c
167 return nb1, nb2
169 def count(self):
170 """
171 @return la population
172 """
173 d = {}
174 for line in self.mat:
175 for c in line:
176 if c not in d:
177 d[c] = 1
178 else:
179 d[c] += 1
180 return d
183class VilleImage(Ville):
184 """
185 Définit une ville à partir d'une image (donc non aléatoire).
186 """
188 def __init__(self, image,
189 cote=100,
190 group=3,
191 taille=3,
192 riche=False,
193 th2=1.2,
194 renouvellement=0.15,
195 delay=1):
196 """
197 constructeur
199 @param image nom d'une image pour définir l'initialisation
200 @param cote cote du carré utilisé pour la simulation
201 @param group nombre de catégories de gens
202 @param taille chaque individu regarde ses voisins à +- taille près
203 @param riche simulation avec riche ou non
204 @param th2 le voisin le plus pauvre peut-être contaminé,
205 si la différence de classes est importante (cl1 > cl2 * th2)
206 @param renouvellement à chaque itération, une certaine proportion des pâtés sont mis à jour,
207 cette proportion correspond à renouvellement
208 @param delay la simulation prend en compte la ville lors des "delay" dernières itérations
210 On tire au hasard la classe d'un pâté de maison dans un disque de rayon cote.
211 """
212 Ville.__init__(self, cote, group, taille, riche,
213 th2, renouvellement, delay)
214 self._initialisation(image)
216 def _initialisation(self, im):
217 for i in range(0, len(self.mat)):
218 for j in range(0, len(self.mat[0])):
219 p = im.get_at((i, j))
221 mins = 1e6
222 best = None
223 for k, v in Ville.colors_grade.items():
224 s = 0
225 for z in [0, 1, 2]:
226 s += (v[z] - p[z]) ** 2
227 s = s ** 0.5
228 if s < mins:
229 mins = s
230 best = k
231 self.mat[i][j] = best
234def display(self, screen, x, pygame):
235 """
236 affichage
237 @param screen écran
238 @param x dimension d'un pâté de maison
239 """
240 screen.fill((0, 0, 0))
241 if self.riche:
242 colors = Ville.colors_grade
243 else:
244 colors = Ville.colors
245 for i in range(0, len(self.mat)):
246 for j in range(0, len(self.mat[i])):
247 c = colors[self.mat[i][j]]
248 pygame.draw.rect(screen, c, pygame.Rect(i * x, j * x, x, x))
251def pygame_simulation(pygame, first_click=False, folder=None,
252 x=6, nb=100, group=6, max_iter=150, th2=1.75,
253 image=None, flags=0, fLOG=noLOG):
254 """
255 Simulation graphique.
256 Illuste la résolution du puzzle
258 @param pygame module pygame
259 @param first_click attend la pression d'un clic de souris avant de commencer
260 @param folder répertoire où stocker les images de la simulation
261 @param size taille de l'écran
262 @param delay delay between two tries
263 @param x pour l'affichage, taille d'un pâté de maison à l'écran
264 @param group ...
265 @param nb taille du carré de la simulation en nombre de pâtés de maisons
266 @param th2 ...
267 @param max_iter nombre d'itérations
268 @param image définition de la ville
269 @param flags see `pygame.display.set_mode <https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode>`_
270 @param fLOG logging function
271 @return @see cl Ville
273 La simulation ressemble à ceci :
275 .. raw:: html
277 <video autoplay="" controls="" loop="" height="500">
278 <source src="http://www.xavierdupre.fr/enseignement/complements/voisinage.mp4" type="video/mp4" />
279 </video>
281 Pour lancer la simulation::
283 from ensae_teaching_cs.special.voisinage_evolution import pygame_simulation
284 import pygame
285 pygame_simulation(pygame)
287 Voir :ref:`l-simulation_voisinage`.
288 """
290 if image is None:
291 this = os.path.dirname(__file__)
292 image = os.path.join(this, "paris_today.png")
294 image = pygame.image.load(image)
295 image = pygame.transform.scale(image, (100, 100))
297 pygame.init()
298 size = nb * x, nb * x
299 screen = pygame.display.set_mode(size, flags)
301 ville = VilleImage(image, nb, group, th2=th2, riche=True)
303 if first_click and pygame is not None:
304 wait_event(pygame)
306 if pygame is not None:
307 display(ville, screen, x, pygame)
308 pygame.display.flip()
309 images = []
310 if folder is not None:
311 images.append(screen.copy())
313 fLOG(ville.count())
314 for i in range(0, max_iter):
315 nb = ville.evolution()
316 fLOG("iteration ", i, " ch ", nb)
317 if pygame is not None:
318 if folder is not None:
319 images.append(screen.copy())
320 display(ville, screen, x, pygame)
321 pygame.display.flip()
322 empty_main_loop(pygame)
324 fLOG(ville.count())
325 if first_click and pygame is not None:
326 wait_event(pygame)
328 if folder is not None and pygame is not None:
329 images.append(screen.copy())
331 if folder is not None:
332 fLOG("saving images")
333 for it, screen in enumerate(images):
334 if it % 10 == 0:
335 fLOG("saving image:", it)
336 image = os.path.join(folder, "image_%04d.png" % it)
337 pygame.image.save(screen, image)
339 return ville