gpt4 book ai didi

algorithm - 网格上的2D装箱

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:15:03 38 4
gpt4 key购买 nike

我有一个n×m网格和一个polyominos集合我想知道是否可以将它们打包到网格中:不允许重叠或旋转。
我认为,像大多数包装问题,这个版本是NP难,难以近似,所以我不期待任何疯狂的,但一个算法,可以找到合理的包装在网格上25×25左右,是相当全面的10×10左右将是巨大的。(我的瓷砖大多是四色的,但可能有5-9个以上的方块。)
我将采取任何人提供的:一个算法,一个文件,一个现有的程序,可以适应。

最佳答案

下面是一个类似于SAT-solver方法的原型,它解决了:
先验固定多氨基模式(参见代码中的Constants / Input
如果允许旋转,则必须将旋转件添加到集合中
每个polymino可放置0-inf次
除此之外,没有评分机制:
非覆盖瓷砖的数量被最小化!
考虑到经典的现成的组合优化方法(SATCPMIP),这个方法可能是最好的(有根据的猜测)在设计定制的启发式算法时也很难打败它!
如果需要,这些幻灯片为SAT解算器提供了一些实践中的practical introduction在这里,我们使用的是基于CDCL的完整解算器(如果有一个解,则总是在有限时间内找到;如果没有,则总是能够证明在有限时间内没有解;当然,内存也起着作用!)是的。
更复杂(线性)的每个瓷砖评分函数一般很难合并这就是(m)ip方法更好的地方。但就纯搜索而言,SAT求解通常要快得多。
我的polymino集合的N=25问题需要大约1秒的时间(人们可以很容易地在多粒度级别上对其进行平行化->SAT解算器(线程参数)与外循环;后者将在后面解释)。
当然,以下情况成立:
因为这是一个NP难问题,所以会有简单和不简单的例子
我没有用许多不同的多元醇组做科学基准
可以预料,有些集合比其他集合更容易求解
这是一个可能的SAT公式(不是最琐碎的!)无限多的
每种配方各有利弊
想法
一般的方法是创建一个决策问题并将其转换为CNF,然后由高效的SAT解算器(这里:cryptoministat;CNF将位于DIMCAS-CNF format)解决,后者将用作黑盒解算器(没有参数调整!)是的。
由于目标是优化填充瓷砖的数量,并且我们正在使用一个决策问题,我们需要一个外部循环,添加一个使用瓷砖的最小约束并尝试解决它如果不成功,请减少此数字所以通常我们会多次调用sat解算器(从头开始!)是的。
有许多不同的公式/转换为CNF是可能的这里我们使用(二进制)决策变量X来表示位置布局是一个类似元组的polyomino, x_index, y_index(该索引标记某个模式的左上角字段)。在变量的数目和所有多聚氨基的可能位置的数目之间有一对一的映射。
核心思想是:在所有可能的布局组合空间中搜索一个解决方案,这不会使某些约束失效。
此外,我们还有决策变量Y,它表示正在填充一个磁贴有这样的变量。
当可以访问所有可能的位置时,很容易为每个平铺索引(m*n)计算冲突集。给定一些固定的平铺,我们可以检查哪些位置可以填充此平铺,并将问题限制为仅选择其中的M*N。这在<=1上处于活动状态。在(m)ip世界中,这可能被称为碰撞的凸包。
X-约束在SAT求解中普遍存在,许多不同的公式都是可能的天真的编码通常需要指数数量的子句,这很容易变得不可行。使用新的变量,有许多变量子句权衡(参见Tseitin-encoding)是可能的。我正在重用一个(旧代码;我的代码仅限于python2的唯一原因),它在过去对我很有用。它基于将基于硬件的计数器逻辑描述为cnf,并提供了良好的经验和理论性能(见论文)。当然有很多选择。
此外,我们需要强制SAT解算器不要使所有变量都为负。我们必须添加描述以下内容的约束(这是一种方法):
如果使用某个字段:必须至少有一个激活的位置(poly+x+y),这将导致覆盖此字段!
这是一个基本的逻辑含义,很容易表述为一个潜在的大逻辑或
然后只缺少核心循环,尝试填充n个字段,然后填充n-1直到成功。这再次使用了前面提到的n<=k公式。
代码
这是python2代码,它需要运行脚本的目录中的sat解算器cryptominisat 5
我还使用了python优秀的科学堆栈中的工具。

# PYTHON 2!
import math
import copy
import subprocess
import numpy as np
import matplotlib.pyplot as plt # plotting-only
import seaborn as sns # plotting-only
np.set_printoptions(linewidth=120) # more nice console-output

""" Constants / Input
Example: 5 tetrominoes; no rotation """
M, N = 25, 25
polyominos = [np.array([[1,1,1,1]]),
np.array([[1,1],[1,1]]),
np.array([[1,0],[1,0], [1,1]]),
np.array([[1,0],[1,1],[0,1]]),
np.array([[1,1,1],[0,1,0]])]

""" Preprocessing
Calculate:
A: possible placements
B: covered positions
C: collisions between placements
"""
placements = []
covered = []
for p_ind, p in enumerate(polyominos):
mP, nP = p.shape
for x in range(M):
for y in range(N):
if x + mP <= M: # assumption: no zero rows / cols in each p
if y + nP <= N: # could be more efficient
placements.append((p_ind, x, y))
cover = np.zeros((M,N), dtype=bool)
cover[x:x+mP, y:y+nP] = p
covered.append(cover)
covered = np.array(covered)

collisions = []
for m in range(M):
for n in range(N):
collision_set = np.flatnonzero(covered[:, m, n])
collisions.append(collision_set)

""" Helper-function: Cardinality constraints """
# K-ARY CONSTRAINT GENERATION
# ###########################
# SINZ, Carsten. Towards an optimal CNF encoding of boolean cardinality constraints.
# CP, 2005, 3709. Jg., S. 827-831.

def next_var_index(start):
next_var = start
while(True):
yield next_var
next_var += 1

class s_index():
def __init__(self, start_index):
self.firstEnvVar = start_index

def next(self,i,j,k):
return self.firstEnvVar + i*k +j

def gen_seq_circuit(k, input_indices, next_var_index_gen):
cnf_string = ''
s_index_gen = s_index(next_var_index_gen.next())

# write clauses of first partial sum (i.e. i=0)
cnf_string += (str(-input_indices[0]) + ' ' + str(s_index_gen.next(0,0,k)) + ' 0\n')
for i in range(1, k):
cnf_string += (str(-s_index_gen.next(0, i, k)) + ' 0\n')

# write clauses for general case (i.e. 0 < i < n-1)
for i in range(1, len(input_indices)-1):
cnf_string += (str(-input_indices[i]) + ' ' + str(s_index_gen.next(i, 0, k)) + ' 0\n')
cnf_string += (str(-s_index_gen.next(i-1, 0, k)) + ' ' + str(s_index_gen.next(i, 0, k)) + ' 0\n')
for u in range(1, k):
cnf_string += (str(-input_indices[i]) + ' ' + str(-s_index_gen.next(i-1, u-1, k)) + ' ' + str(s_index_gen.next(i, u, k)) + ' 0\n')
cnf_string += (str(-s_index_gen.next(i-1, u, k)) + ' ' + str(s_index_gen.next(i, u, k)) + ' 0\n')
cnf_string += (str(-input_indices[i]) + ' ' + str(-s_index_gen.next(i-1, k-1, k)) + ' 0\n')

# last clause for last variable
cnf_string += (str(-input_indices[-1]) + ' ' + str(-s_index_gen.next(len(input_indices)-2, k-1, k)) + ' 0\n')

return (cnf_string, (len(input_indices)-1)*k, 2*len(input_indices)*k + len(input_indices) - 3*k - 1)

def gen_at_most_n_constraints(vars, start_var, n):
constraint_string = ''
used_clauses = 0
used_vars = 0
index_gen = next_var_index(start_var)
circuit = gen_seq_circuit(n, vars, index_gen)
constraint_string += circuit[0]
used_clauses += circuit[2]
used_vars += circuit[1]
start_var += circuit[1]

return [constraint_string, used_clauses, used_vars, start_var]

def parse_solution(output):
# assumes there is one
vars = []
for line in output.split("\n"):
if line:
if line[0] == 'v':
line_vars = list(map(lambda x: int(x), line.split()[1:]))
vars.extend(line_vars)
return vars

def solve(CNF):
p = subprocess.Popen(["cryptominisat5.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
result = p.communicate(input=CNF)[0]
sat_line = result.find('s SATISFIABLE')
if sat_line != -1:
# solution found!
vars = parse_solution(result)
return True, vars
else:
return False, None

""" SAT-CNF: BASE """
X = np.arange(1, len(placements)+1) # decision-vars
# 1-index for CNF
Y = np.arange(len(placements)+1, len(placements)+1 + M*N).reshape(M,N)
next_var = len(placements)+1 + M*N # aux-var gen
n_clauses = 0

cnf = '' # slow string appends
# int-based would be better
# <= 1 for each collision-set
for cset in collisions:
constraint_string, used_clauses, used_vars, next_var = \
gen_at_most_n_constraints(X[cset].tolist(), next_var, 1)
n_clauses += used_clauses
cnf += constraint_string

# if field marked: one of covering placements active
for x in range(M):
for y in range(N):
covering_placements = X[np.flatnonzero(covered[:, x, y])] # could reuse collisions
clause = str(-Y[x,y])
for i in covering_placements:
clause += ' ' + str(i)
clause += ' 0\n'
cnf += clause
n_clauses += 1

print('BASE CNF size')
print('clauses: ', n_clauses)
print('vars: ', next_var - 1)

""" SOLVE in loop -> decrease number of placed-fields until SAT """
print('CORE LOOP')
N_FIELD_HIT = M*N
while True:
print(' N_FIELDS >= ', N_FIELD_HIT)
# sum(y) >= N_FIELD_HIT
# == sum(not y) <= M*N - N_FIELD_HIT
cnf_final = copy.copy(cnf)
n_clauses_final = n_clauses

if N_FIELD_HIT == M*N: # awkward special case
constraint_string = ''.join([str(y) + ' 0\n' for y in Y.ravel()])
n_clauses_final += N_FIELD_HIT
else:
constraint_string, used_clauses, used_vars, next_var = \
gen_at_most_n_constraints((-Y).ravel().tolist(), next_var, M*N - N_FIELD_HIT)
n_clauses_final += used_clauses

n_vars_final = next_var - 1
cnf_final += constraint_string
cnf_final = 'p cnf ' + str(n_vars_final) + ' ' + str(n_clauses) + \
' \n' + cnf_final # header

status, sol = solve(cnf_final)
if status:
print(' SOL found: ', N_FIELD_HIT)

""" Print sol """
res = np.zeros((M, N), dtype=int)
counter = 1
for v in sol[:X.shape[0]]:
if v>0:
p, x, y = placements[v-1]
pM, pN = polyominos[p].shape
poly_nnz = np.where(polyominos[p] != 0)
x_inds, y_inds = x+poly_nnz[0], y+poly_nnz[1]
res[x_inds, y_inds] = p+1
counter += 1
print(res)

""" Plot """
# very very ugly code; too lazy
ax1 = plt.subplot2grid((5, 12), (0, 0), colspan=11, rowspan=5)
ax_p0 = plt.subplot2grid((5, 12), (0, 11))
ax_p1 = plt.subplot2grid((5, 12), (1, 11))
ax_p2 = plt.subplot2grid((5, 12), (2, 11))
ax_p3 = plt.subplot2grid((5, 12), (3, 11))
ax_p4 = plt.subplot2grid((5, 12), (4, 11))
ax_p0.imshow(polyominos[0] * 1, vmin=0, vmax=5)
ax_p1.imshow(polyominos[1] * 2, vmin=0, vmax=5)
ax_p2.imshow(polyominos[2] * 3, vmin=0, vmax=5)
ax_p3.imshow(polyominos[3] * 4, vmin=0, vmax=5)
ax_p4.imshow(polyominos[4] * 5, vmin=0, vmax=5)
ax_p0.xaxis.set_major_formatter(plt.NullFormatter())
ax_p1.xaxis.set_major_formatter(plt.NullFormatter())
ax_p2.xaxis.set_major_formatter(plt.NullFormatter())
ax_p3.xaxis.set_major_formatter(plt.NullFormatter())
ax_p4.xaxis.set_major_formatter(plt.NullFormatter())
ax_p0.yaxis.set_major_formatter(plt.NullFormatter())
ax_p1.yaxis.set_major_formatter(plt.NullFormatter())
ax_p2.yaxis.set_major_formatter(plt.NullFormatter())
ax_p3.yaxis.set_major_formatter(plt.NullFormatter())
ax_p4.yaxis.set_major_formatter(plt.NullFormatter())

mask = (res==0)
sns.heatmap(res, cmap='viridis', mask=mask, cbar=False, square=True, linewidths=.1, ax=ax1)
plt.tight_layout()
plt.show()
break

N_FIELD_HIT -= 1 # binary-search could be viable in some cases
# but beware the empirical asymmetry in SAT-solvers:
# finding solution vs. proving there is none!

输出控制台
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]]

输出图
enter image description here
此参数化中不能包含一个字段!
其他一些具有更大模式集的示例
Square n<=k(prime->invention:harder),其中基CNF有450.723个子句和185.462个变量有一个最佳的包装!
enter image description here
非方 M=N=61(双素数),其中基cnf有1.346.511个子句和553.748个变量。有一个最佳的包装!
enter image description here

关于algorithm - 网格上的2D装箱,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47918792/

38 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com