J'aurais besoin d'aide pour implémenter un algorithme permettant de générer des plans de construction, sur lequel j'ai récemment trébuché en lisant la dernière publication du professeur Kostas Terzidis: Permutation Design: Buildings, Texts and Contexts (2014).
Contexte [~ # ~] [~ # ~]
Citant le professeur Terzidis:
"Un moyen de résoudre ce problème consiste à placer stochastiquement des espaces dans la grille jusqu'à ce que tous les espaces soient ajustés et que les contraintes soient satisfaites"
La figure ci-dessus montre un tel problème et un exemple de solution (f).
ALGORITHME (comme brièvement décrit dans le livre)
1/"Chaque espace est associé à une liste qui contient tous les autres espaces triés selon leur degré de voisinage souhaitable."
2/"Ensuite, chaque unité de chaque espace est sélectionnée dans la liste, puis une par une placée au hasard dans le site jusqu'à ce qu'elles s'intègrent dans le site et que les conditions voisines soient remplies. (En cas d'échec, le processus est répété)"
Exemple de neuf plans générés aléatoirement:
Je dois ajouter que l'auteur explique plus tard que cet algorithme ne repose pas sur des techniques de force brute .
[~ # ~] problèmes [~ # ~]
Comme vous pouvez le voir, l'explication est relativement vague et l'étape 2 est assez peu claire (en termes de codage). Tout ce que j'ai à ce jour, ce sont des "pièces d'un puzzle":
pour chaque unité:
un score de fitness basé sur ses voisins réels
from random import shuffle
n_col, n_row = 7, 5
to_skip = [0, 1, 21, 22, 23, 24, 28, 29, 30, 31]
site = [i for i in range(n_col * n_row) if i not in to_skip]
fitness, grid = [[None if i in to_skip else [] for i in range(n_col * n_row)] for e in range(2)]
n = 2
k = (n_col * n_row) - len(to_skip)
rsize = 50
#Adjacency matrix
adm = [[0, 6, 1, 5, 2],
[6, 0, 1, 4, 0],
[1, 1, 0, 8, 0],
[5, 4, 8, 0, 3],
[2, 0, 0, 3, 0]]
spaces = {"office1": [0 for i in range(4)],
"office2": [1 for i in range(6)],
"office3": [2 for i in range(6)],
"passage": [3 for i in range(7)],
"entry": [4 for i in range(2)]}
def setup():
global grid
size(600, 400, P2D)
rectMode(CENTER)
strokeWeight(1.4)
#Shuffle the order for the random placing to come
shuffle(site)
#Place units randomly within the limits of the site
i = -1
for space in spaces.items():
for unit in space[1]:
i+=1
grid[site[i]] = unit
#For each unit of each space...
i = -1
for space in spaces.items():
for unit in space[1]:
i+=1
#Get the indices of the its DESIRABLE neighbors in sorted order
ada = adm[unit]
sorted_indices = sorted(range(len(ada)), key = ada.__getitem__)[::-1]
#Select indices with positive weight (exluding 0-weight indices)
pindices = [e for e in sorted_indices if ada[e] > 0]
#Stores its fitness score (sum of the weight of its REAL neighbors)
fitness[site[i]] = sum([ada[n] for n in getNeighbors(i) if n in pindices])
print 'Fitness Score:', fitness
def draw():
background(255)
#Grid's background
fill(170)
noStroke()
rect(width/2 - (rsize/2) , height/2 + rsize/2 + n_row , rsize*n_col, rsize*n_row)
#Displaying site (grid cells of all selected units) + units placed randomly
for i, e in enumerate(grid):
if isinstance(e, list): pass
Elif e == None: pass
else:
fill(50 + (e * 50), 255 - (e * 80), 255 - (e * 50), 180)
rect(width/2 - (rsize*n_col/2) + (i%n_col * rsize), height/2 + (rsize*n_row/2) + (n_row - ((k+len(to_skip))-(i+1))/n_col * rsize), rsize, rsize)
fill(0)
text(e+1, width/2 - (rsize*n_col/2) + (i%n_col * rsize), height/2 + (rsize*n_row/2) + (n_row - ((k+len(to_skip))-(i+1))/n_col * rsize))
def getNeighbors(i):
neighbors = []
if site[i] > n_col and site[i] < len(grid) - n_col:
if site[i]%n_col > 0 and site[i]%n_col < n_col - 1:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if site[i] <= n_col:
if site[i]%n_col > 0 and site[i]%n_col < n_col - 1:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if site[i]%n_col == 0:
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if site[i] == n_col-1:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if site[i] >= len(grid) - n_col:
if site[i]%n_col > 0 and site[i]%n_col < n_col - 1:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
if site[i]%n_col == 0:
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
if site[i]%n_col == n_col-1:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
if site[i]%n_col == 0:
if site[i] > n_col and site[i] < len(grid) - n_col:
if grid[site[i]+1] != None: neighbors.append(grid[site[i]+1])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
if site[i]%n_col == n_col - 1:
if site[i] > n_col and site[i] < len(grid) - n_col:
if grid[site[i]-1] != None: neighbors.append(grid[site[i]-1])
if grid[site[i]+n_col] != None: neighbors.append(grid[site[i]+n_col])
if grid[site[i]-n_col] != None: neighbors.append(grid[site[i]-n_col])
return neighbors
J'apprécierais vraiment si quelqu'un pouvait aider à relier les points et m'expliquer:
[~ # ~] modifier [~ # ~]
Comme certains d'entre vous l'ont remarqué, l'algorithme est basé sur la probabilité que certains espaces (composés d'unités) soient adjacents. La logique voudrait alors que chaque unité se place aléatoirement dans les limites du site:
En gros, cela se traduirait par ceci:
i = -1
for space in spaces.items():
for unit in space[1]:
i+=1
#Get the indices of the its DESIRABLE neighbors (from the adjacency matrix 'adm') in sorted order
weights = adm[unit]
sorted_indices = sorted(range(len(weights)), key = weights.__getitem__)[::-1]
#Select indices with positive weight (exluding 0-weight indices)
pindices = [e for e in sorted_indices if weights[e] > 0]
#If random grid cell is empty
if not grid[site[i]]:
#List of neighbors
neighbors = [n for n in getNeighbors(i) if isinstance(n, int)]
#If no neighbors -> place unit
if len(neighbors) == 0:
grid[site[i]] = unit
#If at least 1 of the neighbors == unit: -> place unit (facilitate grouping)
if len(neighbors) > 0 and unit in neighbors:
grid[site[i]] = unit
#If 2 or 3 neighbors, compute fitness score and place unit if probability is high
if len(neighbors) >= 2 and len(neighbors) < 4:
fscore = sum([weights[n] for n in neighbors if n in pindices]) #cumulative weight of its ACTUAL neighbors
count = [1 for t in range(10) if random(sum(weights)) < fscore] #add 1 if fscore higher than a number taken at random between 0 and the cumulative weight of its DESIRABLE neighbors
if len(count) > 5:
grid[site[i]] = unit
#If 4 neighbors and high probability, 1 of them must belong to the same space
if len(neighbors) > 3:
fscore = sum([weights[n] for n in neighbors if n in pindices]) #cumulative weight of its ACTUAL neighbors
count = [1 for t in range(10) if random(sum(weights)) < fscore] #add 1 if fscore higher than a number taken at random between 0 and the cumulative weight of its DESIRABLE neighbors
if len(count) > 5 and unit in neighbors:
grid[site[i]] = unit
#if random grid cell not empty -> pass
else: pass
Étant donné qu'une partie importante des unités ne sera pas placée lors de la première exécution (en raison de la faible probabilité d'adjacence), nous devons répéter indéfiniment jusqu'à ce qu'une distribution aléatoire où toutes les unités puissent être ajustées soit trouvée.
Après quelques milliers d'itérations, un ajustement est trouvé et toutes les exigences voisines sont satisfaites.
Notez cependant comment cet algorithme produit des groupes séparés au lieu de piles non divisées et uniformes comme dans l'exemple fourni. Je dois également ajouter que près de 5 000 itérations, c'est beaucoup plus que les 274 itérations mentionnées par M. Terzidis dans son livre.
Questions:
La solution que je propose pour résoudre ce défi est basée sur la répétition de l'algorithme plusieurs fois tout en enregistrant des solutions valides. Comme la solution n'est pas unique, je m'attends à ce que l'algorithme lance plus d'une solution. Chacun d'eux aura un score basé sur l'affinité des voisins.
J'appellerai une ' tentative ' pour une exécution complète essayant de trouver une distribution de plante valide. L'exécution complète du script consistera en N
tentatives.
Chaque tentative commence par 2 choix aléatoires (uniformes):
Une fois défini un point et un bureau, il arrive un ' processus d'expansion ' essayant d'adapter tous les blocs de bureaux dans la grille.
Chaque nouveau bloc est défini selon sa procédure:
Après chaque immeuble de bureaux est placé, un autre choix aléatoire uniforme est nécessaire: le prochain bureau à placer.
Une fois choisi, vous devez calculer à nouveau l'affinité pour chaque site et sélectionner au hasard (pondéré) le point de départ du nouveau bureau.
0
les bureaux d'affinité n'ajoutent rien. Le facteur de probabilité doit être0
pour ce point de la grille. La sélection de la fonction d'affinité fait partie intégrante de ce problème. Vous pouvez essayer d'ajouter ou même de multiplier le facteur de cellules adjacentes.
Le processus d'expansion recommence jusqu'à ce que chaque bloc du bureau soit placé.
Donc, fondamentalement, la sélection des bureaux suit une distribution uniforme et, après cela, le processus d'expansion pondéré se produit pour le bureau sélectionné.
Quand se termine une tentative? , si:
affinity = 0
)Ensuite, la tentative n'est pas valide et doit être ignorée pour passer à une nouvelle tentative aléatoire.
Sinon, si tous les blocs sont en forme: c'est valide.
Le fait est que les bureaux doivent rester unis. C'est le point clé de l'algorithme, qui essaie au hasard d'adapter chaque nouveau bureau en fonction de l'affinité mais toujours un processus aléatoire. Si les conditions ne sont pas remplies (non valides), le processus aléatoire recommence en choisissant un point de grille et un bureau nouvellement aléatoires.
Désolé, il n'y a qu'un algorithme mais rien de code ici.
Remarque: Je suis sûr que le processus de calcul d'affinité pourrait être amélioré ou même vous pourriez essayer avec différentes méthodes. Ce n'est qu'une idée pour vous aider à trouver votre solution.
J'espère que cela aide.
Je suis sûr que le professeur Kostas Terzidis serait un excellent chercheur en théorie informatique, mais ses explications sur les algorithmes ne sont d'aucune utilité.
Premièrement, la matrice d'adjacence n'a aucun sens. Dans les commentaires, vous avez dit:
"plus cette valeur est élevée, plus la probabilité que les deux espaces soient adjoints est élevée"
mais m[i][i] = 0
, ce qui signifie que les personnes dans le même "bureau" préfèrent d'autres bureaux que le voisin. C'est exactement le contraire que vous attendez, n'est-ce pas? Je suggère d'utiliser cette matrice à la place:
With 1 <= i, j <= 5:
+----------------+
| 10 6 1 5 2 |
| 10 1 4 0 |
m[i][j] = | 10 8 0 |
| 10 3 |
| 10 |
+----------------+
Avec cette matrice,
m[i][i] = 10
Signifie exactement ce que vous voulez: les personnes dans le même bureau doivent être ensemble.Étape 1: commencez à mettre tous les endroits au hasard
(Désolé pour l'indexation matricielle basée sur 1, mais cela doit être cohérent avec la matrice d'adjacence.)
With 1 <= x <= 5 and 1 <= y <= 7:
+---------------------+
| - - 1 2 1 4 3 |
| 1 2 4 5 1 4 3 |
p[x][y] = | 2 4 2 4 3 2 4 |
| - - - - 3 2 4 |
| - - - - 5 3 3 |
+---------------------+
Étape 2: noter la solution
Pour tous places p[x][y]
, Calculez le score en utilisant la matrice d'adjacence. Par exemple, la première place 1
A 2
Et 4
Comme voisins, donc le score est 11:
score(p[1][3]) = m[1][2] + m[1][4] = 11
La somme de tous les scores individuels serait le score de la solution.
Étape 3: affiner la solution actuelle en changeant de place
Pour chaque paire de places p[x1][y1], p[x2][y2]
, Échangez-les et réévaluez la solution, si le score est meilleur, conservez la nouvelle solution. Dans tous les cas, répétez l'étape 3 jusqu'à ce qu'aucune permutation ne soit en mesure d'améliorer la solution.
Par exemple, si vous échangez p[1][4]
Avec p[2][1]
:
+---------------------+
| - - 1 1 1 4 3 |
| 2 2 4 5 1 4 3 |
p[x][y] = | 2 4 2 4 3 2 4 |
| - - - - 3 2 4 |
| - - - - 5 3 3 |
+---------------------+
vous trouverez une solution avec un meilleur score:
avant échange
score(p[1][3]) = m[1][2] + m[1][4] = 11
score(p[2][1]) = m[1][2] + m[1][2] = 12
après échange
score(p[1][3]) = m[1][1] + m[1][4] = 15
score(p[2][1]) = m[2][2] + m[2][2] = 20
Alors gardez-le et continuez à changer de place.
N
places, il y a N x (N-1)
swaps possibles, et cela peut être fait de manière efficace (donc, aucune force brute n'est nécessaire).J'espère que cela aide!