Algorithm 网格上的二维装箱

Algorithm 网格上的二维装箱,algorithm,mathematical-optimization,discrete-mathematics,bin-packing,Algorithm,Mathematical Optimization,Discrete Mathematics,Bin Packing,我有一个n×m的网格和一个。我想知道是否可以将它们打包到网格中:不允许重叠或旋转 我希望像大多数的填充问题一样,这个版本是NP难的,很难近似,所以我不希望有什么疯狂的事情发生,但是一个能够在25×25左右的网格上找到合理的填充,并且在10×10左右相当全面的算法将是非常棒的。(我的瓷砖大部分是tetrominos——四块——但可能有5-9块以上。) 我将接受任何人提供的任何东西:一个算法、一篇论文、一个可以修改的现有程序。我不知道它是否对您有用,但我用Python编写了一个小的粗略框架。它还没有

我有一个n×m的网格和一个。我想知道是否可以将它们打包到网格中:不允许重叠或旋转

我希望像大多数的填充问题一样,这个版本是NP难的,很难近似,所以我不希望有什么疯狂的事情发生,但是一个能够在25×25左右的网格上找到合理的填充,并且在10×10左右相当全面的算法将是非常棒的。(我的瓷砖大部分是tetrominos——四块——但可能有5-9块以上。)


我将接受任何人提供的任何东西:一个算法、一篇论文、一个可以修改的现有程序。

我不知道它是否对您有用,但我用Python编写了一个小的粗略框架。它还没有放置polyminos,但是函数已经存在了——不过检查死空空间很简单,需要更好的方法。再说一遍,也许这都是垃圾

import functools
import itertools

M = 4 # x
N = 5 # y

field = [[9999]*(N+1)]+[[9999]+[0]*N+[9999] for _ in range(M)]+[[9999]*(N+1)]

def field_rd(p2d):
    return field[p2d[0]+1][p2d[1]+1]

def field_add(p2d,val):
    field[p2d[0]+1][p2d[1]+1] += val

def add2d(p,k):
    return p[0]+k[0],p[1]+k[1]

def norm(polymino_2d):
    x0,y0 = min(x for x,y in polymino_2d),min(y for x,y in polymino_2d)
    return tuple(sorted(map(lambda p: add2d(p,(-x0,-y0)), polymino_2d)))

def create_cutoff(occupied):
    """Receive a polymino and create the outer area of squares which could be cut off by a placement of this polymino"""
    cutoff = set(itertools.chain.from_iterable(map(lambda p: add2d(p,(x,y)),occupied) for (x,y) in [(-1,0),(1,0),(0,-1),(0,1)])) #(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1)]))
    return tuple(cutoff.difference(occupied))

def is_occupied(p2d):
    return field_rd(p2d) == 0

def is_cutoff(p2d):
    return not is_occupied(p2d) and all(map(is_occupied,map(lambda p: add2d(p,p2d),[(-1,0),(1,0),(0,-1),(0,1)])))

def polym_colliding(p2d,occupied):
    return any(map(is_occupied,map(lambda p: add2d(p,p2d),occupied)))

def polym_cutoff(p2d,cutoff):
    return any(map(is_cutoff,map(lambda p: add2d(p,p2d),cutoff)))

def put(p2d,occupied,polym_nr):
    for p in occupied:
        field_add(add2d(p2d,p),polym_nr)

def remove(p2d,occupied,polym_nr):
    for p in polym:
        field_add(add2d(p2d,p),-polym_nr)

def place(p2d,polym_nr):
    """Try to place a polymino at point p2d. If it fits without cutting off unreachable single cells return True else False"""
    occupied = polym[polym_nr][0]
    if polym_colliding(p2d,occupied):
        return False
    put(p2d,occupied,polym_nr)
    cutoff = polym[polym_nr][1]
    if polym_cutoff(p2d,cutoff):
        remove(p2d,occupied,polym_nr)
        return False
    return True

def NxM_array(N,M):
    return [[0]*N for _ in range(M)]


def generate_all_polyminos(n):
    """Create all polyminos with size n"""
    def gen_recur(polymino,i,result):
        if i > 1:
            new_pts = set(itertools.starmap(add2d,itertools.product(polymino,[(-1,0),(1,0),(0,-1),(0,1)])))
            new_pts = new_pts.difference(polymino)
            for p in new_pts:
                gen_recur(polymino.union({p}),i-1,result)
        else:
            result.add(norm(polymino))
    #---------------------------------------
    all_polyminos = set()
    gen_recur({(0,0)},n,all_polyminos)
    return all_polyminos

print("All possible Tetris blocks (all orientations): ",generate_all_polyminos(4))

一种方法可以是使用整数规划。我将使用pythonplup包实现这一点,尽管包可用于几乎任何编程语言

基本思想是为每个瓷砖的每个可能放置位置定义一个决策变量。如果决策变量的值为1,则其关联的磁贴将放置在那里。如果值为0,则不将其放置在该位置。因此,目标是最大化决策变量的总和乘以变量平铺中的方块数——这对应于在板上放置最大可能的方块数

我的代码实现了两个约束:

  • 每个瓷砖只能放置一次(下面我们将放宽此限制)
  • 每个正方形上最多可以有一块瓷砖
以下是4x5网格上一组五个固定四溴甲烷的输出:

导入itertools
进口纸浆
导入字符串
def覆盖(瓷砖、底座):
返回{(基[0]+t[0],基[1]+t[1]):对于tile中的t为True}
瓷砖=[(0,0)、(1,0)、(0,1)、(0,2)],
[(0,0), (1,0), (2,0), (3,0)],
[(1,0), (0,1), (1,1), (2,0)],
[(0,0), (1,0), (0,1), (1,1)],
[(1,0), (0,1), (1,1), (2,1)]]
行=25
cols=25
squares={x:itertools.product(范围(行)、范围(列))中的x为True}
vars=list(itertools.product(范围(行)、范围(列)、范围(列(平铺)))
变量=[x表示变量中的x,如果所有([y表示覆盖中的y(平铺[x[2]],(x[0],x[1])。键()])]
x=palp.LpVariable.dicts('tiles',vars,lowBound=0,upBound=1,cat=palp.LpInteger)
mod=纸浆.LpProblem('Polyminoes',纸浆.LpProblem)
#目标值是平铺中的方块数
mod+=sum([len(tiles[p[2]])*x[p]表示变量中的p])
#不要使用任何形状超过一次
对于范围内的tnum(透镜(瓷砖)):
mod+=sum([x[p]表示变量中的p,如果p[2]==tnum])这里是一种类似原型的方法,它解决:

  • 先验固定的Polymino模式(参见代码中的常数/输入)
    • 如果允许旋转,则必须将旋转的工件添加到集合中
  • 每个Polymino可以放置0-inf次
  • 除此之外,没有评分机制:
    • 将未覆盖瓷砖的数量降至最低
考虑到经典的现成的组合优化方法(,),这一方法可能是规模最大的(有根据的猜测)。在设计定制的启发式算法时,它也很难被击败

如果需要,这些幻灯片为SAT解算器在实践中提供了一些帮助。在这里,我们使用的是基于完整的解算器(如果有解,总是在有限时间内找到解;如果没有解,总是能够证明在有限时间内没有解;当然,记忆也起着作用!)

更复杂的(线性)每块记分函数通常很难合并。这就是(M)IP方法可以更好的地方。但就纯搜索而言,SAT求解通常要快得多

我的PolyMino集合的
N=25
问题需要~1秒(人们可以很容易地在多粒度级别上对其进行并行分析->SAT解算器(threadings param)与外部循环;后者将在后面解释)

当然,以下观点是正确的:

  • 由于这是一个NP难问题,将有容易和不容易的例子
  • 我没有对许多不同的Polyminos进行科学基准测试
    • 可以预期,某些集合比其他集合更容易求解
  • 这是一个可能的SAT公式(不是最琐碎的!)
    • 每种配方都有优点和缺点
主意 一般的方法是创建一个决策问题并将其转化为,然后由高效的SAT解算器(此处:CryptoMinistat;CNF将在中)解决,该解算器将用作黑盒解算器(无参数调整!)

由于目标是优化填充瓷砖的数量,并且我们正在使用一个决策问题,因此我们需要一个外部循环,添加最小瓷砖使用量约束,并尝试解决它。如果未成功,请减少此数字。因此,通常我们会多次调用SAT解算器(从头开始!)

CNF可能有许多不同的配方/转换。在这里,我们使用(二进制)决策变量
X
,它们表示位置。位置是一个元组,如
polymino,x_索引,y_索引
(该索引标记某些模式的左上角字段)。变量数量与所有PolyMinos的可能位置数量之间存在一对一映射关系

其核心思想是:在所有可能的布局组合空间中搜索一个解决方案,这不会使某些约束失效

此外,我们还有决策变量
Y
,它们指示正在填充的磁贴。有
M*N
这样的变量

当可以访问所有可能的位置时,很容易为每个平铺索引(M*N)计算碰撞集。给定一些固定的瓷砖,我们可以
AAAB-
A-BBC
DDBCC
DD--C
ABCDD
ABCDD
ABCEE
ABCEE
ABCCDDEEFF
ABCCDDEEFF
ABGHHIJJKK
ABGHHIJJKK
LLGMMINOPP
LLGMMINOPP
QQRRSTNOUV
QQRRSTNOUV
WWXXSTYYUV
WWXXSTYYUV
BASE CNF size
('clauses: ', 31509)
('vars: ', 13910)
CORE LOOP
(' N_FIELDS >= ', 625)
(' N_FIELDS >= ', 624)
(' SOL found: ', 624)
[[3 2 2 2 2 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 2 2]
 [3 2 2 2 2 1 1 1 1 1 1 1 1 2 2 2 2 2 2 1 1 1 1 2 2]
 [3 3 3 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1 2 2]
 [2 2 3 1 1 1 1 1 1 1 1 2 2 2 2 1 1 1 1 2 2 2 2 2 2]
 [2 2 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2]
 [1 1 1 1 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2]
 [1 1 1 1 3 3 3 2 2 1 1 1 1 2 2 2 2 2 2 2 2 1 1 1 1]
 [2 2 1 1 1 1 3 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2]
 [2 2 2 2 2 2 3 3 3 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2]
 [2 2 2 2 2 2 2 2 3 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2]
 [2 2 1 1 1 1 2 2 3 3 3 2 2 2 2 2 2 1 1 1 1 2 2 2 2]
 [1 1 1 1 1 1 1 1 2 2 3 2 2 1 1 1 1 1 1 1 1 1 1 1 1]
 [2 2 3 1 1 1 1 3 2 2 3 3 4 1 1 1 1 2 2 1 1 1 1 2 2]
 [2 2 3 1 1 1 1 3 1 1 1 1 4 4 3 2 2 2 2 1 1 1 1 2 2]
 [2 2 3 3 5 5 5 3 3 1 1 1 1 4 3 2 2 1 1 1 1 1 1 1 1]
 [2 2 2 2 4 5 1 1 1 1 1 1 1 1 3 3 3 2 2 1 1 1 1 2 2]
 [2 2 2 2 4 4 2 2 1 1 1 1 1 1 1 1 3 2 2 1 1 1 1 2 2]
 [2 2 2 2 3 4 2 2 2 2 2 2 1 1 1 1 3 3 3 2 2 2 2 2 2]
 [3 4 2 2 3 5 5 5 2 2 2 2 1 1 1 1 2 2 3 2 2 2 2 2 2]
 [3 4 4 3 3 3 5 5 5 5 1 1 1 1 2 2 2 2 3 3 3 2 2 2 2]
 [3 3 4 3 1 1 1 1 5 1 1 1 1 4 2 2 2 2 2 2 3 2 2 2 2]
 [2 2 3 3 3 1 1 1 1 1 1 1 1 4 4 4 2 2 2 2 3 3 0 2 2]
 [2 2 3 1 1 1 1 1 1 1 1 5 5 5 4 4 4 1 1 1 1 2 2 2 2]
 [2 2 3 3 1 1 1 1 1 1 1 1 5 5 5 5 4 1 1 1 1 2 2 2 2]
 [2 2 1 1 1 1 1 1 1 1 1 1 1 1 5 1 1 1 1 1 1 1 1 2 2]]