web-dev-qa-db-fra.com

Comment réorganiser les unités en fonction de leur degré de voisinage souhaitable? (dans le traitement)

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 [~ # ~] [~ # ~]

  • Considérons un site (b) divisé en un système de grille (a).
  • Considérons également une liste d'espaces à placer dans les limites du site (c) et une matrice d'adjacence pour déterminer les conditions de placement et les relations voisines de ces espaces (d)

enter image description here

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:

enter image description here

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":

  • un "site" (liste des entiers sélectionnés)
  • une matrice d'adjacence (listes imbriquées)
  • "espaces" (dictionnaire des listes)

pour chaque unité:

  • une fonction qui renvoie ses voisins directs
  • une liste de ses voisins souhaitables avec leurs indices dans l'ordre trié
  • 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
    

enter image description here

J'apprécierais vraiment si quelqu'un pouvait aider à relier les points et m'expliquer:

  • comment réorganiser les unités en fonction de leur degré de voisinage souhaitable?

[~ # ~] 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:

  • on vérifie au préalable ses voisins directs (haut, bas, gauche droite)
  • calculer un score de fitness si au moins 2 voisins. (= somme des poids de ces 2+ voisins)
  • et enfin placer cette unité si la probabilité d'adjacence est élevée

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.

enter image description here

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:

  • Y a-t-il un problème avec la façon dont j'aborde cet algorithme?
  • Si non, quelle condition implicite suis-je manquante?
31
solub

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):

  • Point de départ dans la grille
  • Bureau de départ

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:

  • 1er. Calculez l'affinité pour chaque cellule adjacente au bureau.
  • 2e. Sélectionnez un site au hasard. Les choix doivent être pondérés par l'affinité.

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 être 0 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:

  • Inutile dans la grille de placer un nouveau bureau (tous ont affinity = 0)
  • Office ne peut pas se développer car tous les poids d'affinité sont égaux à 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.

enter image description here

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.

3
Cheche

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,

  • La valeur la plus élevée est 10. Donc m[i][i] = 10 Signifie exactement ce que vous voulez: les personnes dans le même bureau doivent être ensemble.
  • La valeur la plus basse est 0. (Personnes qui ne devraient avoir aucun contact du tout)

L'algorithme

É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.

Quelques notes

  • Notez que l'algorithme finalisera toujours étant donné qu'à un moment donné de l'itération, vous ne pourrez pas échanger 2 places et obtenir un meilleur score.
  • Dans une matrice avec 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!

0
Cartucho