如何根据所需邻域的程度对单元进行重新排序? (在处理中)

时间:2018-11-03 00:56:48

标签: python processing permutation

我需要帮助来实现允许生成建筑计划的算法,最近在阅读Kostas Terzidis教授的最新出版物Permutation Design: Buildings, Texts and Contexts(2014年)时偶然发现了该算法。

上下文

  • 请考虑将站点(b)划分为网格系统(a)。
  • 我们还考虑要在站点限制范围内放置的空间列表(c)和邻接矩阵,以确定这些空间的放置条件和相邻关系(d)

enter image description here

引用Terzidis教授:

  

“解决此问题的一种方法是在网格内随机放置空间,直到所有空间都适合并且满足约束条件为止”。

上图显示了这样的问题和示例解决方案(f)。

算法(如书中所述)

1 /“每个空间都与一个列表相关联,该列表包含所有其他空间,这些其他空间均根据其所需的邻域程度进行排序。”

2 /“然后从列表中选择每个空间的每个单元,然后将其一个接一个地随机放置在站点中,直到它们适合站点并满足相邻条件为止。(如果失败,则该过程为重复)”

九个随机生成的计划的示例:

enter image description here

我还要补充一点,作者稍后会解释该算法不依赖于蛮力技术

问题

如您所见,解释相对模糊,步骤2 尚不清楚(就编码而言)。到目前为止,我所拥有的只是“一块拼图”:

  • “站点”(选定整数列表)
  • 邻接矩阵(嵌套列表)
  • “空格”(列表的字典)

对于每个单元:

  • 返回其直接邻居的函数
  • 其所需邻居及其索引按排序顺序的列表
  • 基于其实际邻居的健身得分

    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

如果有人可以帮忙说明一下,我将不胜感激:

  • 如何根据所需邻域的程度对单元进行重新排序?

编辑

您中有些人已经注意到,该算法基于某些空间(由单元组成)相邻的可能性。这样一来,逻辑上就可以将每个单元随机放置在站点范围内:

  • 我们预先检查其直接邻居(上,下,左,右)
  • 如果至少有2个邻居,则计算适合度得分。 (=这2个以上邻居的权重之和)
  • 如果邻接概率很高,最后放置该单元

大致上,它会翻译成这样:

    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

鉴于大部分设备不会放在第一次运行中(由于邻接概率低),我们需要反复进行迭代,直到找到可以拟合所有设备的随机分布为止。

enter image description here

经过数千次迭代,找到了一个拟合,并且满足了所有邻近的要求。

但是请注意,此算法如何产生分离的组,而不是像所提供的示例中那样产生未划分且统一的堆栈。我还应该补充说,近5000次迭代比Terzidis先生在书中提到的274次迭代要多得多。

问题:

  • 我处理此算法的方式有问题吗?
  • 如果否,那么我缺少什么隐含条件?

2 个答案:

答案 0 :(得分:3)

我建议解决此挑战的解决方案是基于在记录有效解决方案的同时重复几次算法。由于解决方案不是唯一的,所以我希望算法抛出多个解决方案。他们每个人都有一个基于邻居亲和力的分数。

我将调用“ 尝试”进行完整的尝试,以查找有效的工厂分布。完整的脚本运行将包含N次尝试。

每次尝试均从2个随机(统一)选择开始:

  • 网格中的起点
  • 新办公室

一旦定义了一个点和一个办公室,就会出现一个“ 扩展过程”,试图将所有办公室块放到网格中。

每个新块均根据其过程进行设置:

  • 第一名。计算与办公室相邻的每个小区的亲和力。
  • 第二。随机选择一个站点。选择应通过亲和力加权。

在放置每个办公大楼之后,还需要另一个统一的随机选择:要放置下一个办公室。

选择后,您应该重新计算每个站点的亲和力,并随机(错误地)选择新办公室的起点。

  

0亲和办公室未添加。概率因数应为0   网格中的那个点。亲和力功能选择非常麻烦   这个问题的一部分。您可以尝试添加或什至   邻近细胞倍增因子。

扩展过程将再次参与,直到放置办公室的每个区域。

因此,基本上,办公室的选择遵循统一的分配方式,然后,选定办公室的加权扩展过程就会发生。

尝试何时结束?, 如果:

  • 网格中没有必要放置新办公室(所有人都有affinity = 0
  • 办公室无法扩展,因为所有亲和力权重均等于0

然后,该尝试无效,应将其丢弃,转向全新的随机尝试。

否则,如果所有块都适合:这是有效的。

重点是办公室应该团结在一起。这是算法的关键点,该算法会根据亲和力随机尝试适应每个新办公室,但这仍然是一个随机过程。如果不满足条件(无效),则随机过程会再次开始,选择一个新的随机网格点和办公室。

enter image description here

对不起,这里只有一个算法,但是没有代码。

注意:我敢肯定亲和力计算过程可以得到改善,甚至您可以尝试使用其他方法。这只是一个帮助您获得解决方案的想法。

希望有帮助。

答案 1 :(得分:0)

我相信Kostas Terzidis教授将是一位出色的计算机理论研究人员,但是他的算法解释完全没有帮助。

首先,邻接矩阵没有意义。在问题评论中,您说过:

  

“该值越高,两个空格相邻的可能性就越大”

m[i][i] = 0,这意味着在同一“办公室”中的人们更喜欢其他办公室作为邻居。那与您期望的完全相反,不是吗?我建议改用此矩阵:

With 1 <= i, j <= 5:

              +----------------+
              | 10  6  1  5  2 |
              |    10  1  4  0 |
    m[i][j] = |       10  8  0 |  
              |          10  3 |
              |             10 |
              +----------------+

有了这个矩阵,

  • 最高值为10。所以m[i][i] = 10正是您想要的:同一办公室里的人应该在一起。
  • 最低值为0。(完全不应该联系的人)

算法

第1步:开始随机放置所有地方

(为基于1的矩阵索引感到抱歉,但必须与邻接矩阵保持一致。)

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 |
            +---------------------+

第2步:对解决方案进行评分

对于所有个地方p[x][y],使用邻接矩阵计算分数。例如,第一名1具有24作为邻居,因此得分为11:

score(p[1][3]) = m[1][2] + m[1][4] = 11

所有单个分数的总和就是解决方案分数

步骤3:通过交换位置来优化当前解决方案

对于每对p[x1][y1], p[x2][y2]对场所,将它们交换并再次评估解决方案,如果得分更好,请保留新的解决方案。在任何情况下,请重复执行步骤3,直到没有排列能够改善解的为止。

例如,如果您将p[1][4]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 |
            +---------------------+

您会找到分数更高的解决方案:

交换之前

score(p[1][3]) = m[1][2] + m[1][4] = 11
score(p[2][1]) = m[1][2] + m[1][2] = 12

交换后

score(p[1][3]) = m[1][1] + m[1][4] = 15
score(p[2][1]) = m[2][2] + m[2][2] = 20

因此请保留并继续交换位置。

一些笔记

  • 请注意,由于在迭代的某个时刻您将不能交换2个位置并且得分更高,因此该算法将始终完成。
  • 在具有N个位置的矩阵中,存在N x (N-1)个可能的交换,并且可以以有效的方式完成(因此,不需要蛮力)。

希望有帮助!