Eu sou novo no mundo dos solucionadores de SAT e precisaria de algumas orientações sobre o seguinte problema.
Considerando que:
❶ Eu tenho uma seleção de 14 células adjacentes em uma grade 4 * 4
Have Eu tenho 5 poliaminos (A, B, C, D, E) dos tamanhos 4, 2, 5, 2 e 1
Poly esses poliaminoinos são livres , ou seja, sua forma não é fixa e pode formar padrões diferentes
Como posso calcular todas as combinações possíveis desses 5 poliominos livres dentro da área selecionada (células em cinza) com um solucionador SAT?
Tomando emprestado da resposta perspicaz do @ spinkus e da documentação das ferramentas OR, eu poderia criar o seguinte código de exemplo (executado em um Jupyter Notebook):
from ortools.sat.python import cp_model
import numpy as np
import more_itertools as mit
import matplotlib.pyplot as plt
%matplotlib inline
W, H = 4, 4 #Dimensions of grid
sizes = (4, 2, 5, 2, 1) #Size of each polyomino
labels = np.arange(len(sizes)) #Label of each polyomino
colors = ('#FA5454', '#21D3B6', '#3384FA', '#FFD256', '#62ECFA')
cdict = dict(zip(labels, colors)) #Color dictionary for plotting
inactiveCells = (0, 1) #Indices of disabled cells (in 1D)
activeCells = set(np.arange(W*H)).difference(inactiveCells) #Cells where polyominoes can be fitted
ranges = [(next(g), list(g)[-1]) for g in mit.consecutive_groups(activeCells)] #All intervals in the stack of active cells
def main():
model = cp_model.CpModel()
#Create an Int var for each cell of each polyomino constrained to be within Width and Height of grid.
pminos = [[] for s in sizes]
for idx, s in enumerate(sizes):
for i in range(s):
pminos[idx].append([model.NewIntVar(0, W-1, 'p%i'%idx + 'c%i'%i + 'x'), model.NewIntVar(0, H-1, 'p%i'%idx + 'c%i'%i + 'y')])
#Define the shapes by constraining the cells relative to each other
## 1st polyomino -> tetromino ##
# #
# #
# # #
# ### #
# #
################################
p0 = pminos[0]
model.Add(p0[1][0] == p0[0][0] + 1) #'x' of 2nd cell == 'x' of 1st cell + 1
model.Add(p0[2][0] == p0[1][0] + 1) #'x' of 3rd cell == 'x' of 2nd cell + 1
model.Add(p0[3][0] == p0[0][0] + 1) #'x' of 4th cell == 'x' of 1st cell + 1
model.Add(p0[1][1] == p0[0][1]) #'y' of 2nd cell = 'y' of 1st cell
model.Add(p0[2][1] == p0[1][1]) #'y' of 3rd cell = 'y' of 2nd cell
model.Add(p0[3][1] == p0[1][1] - 1) #'y' of 3rd cell = 'y' of 2nd cell - 1
## 2nd polyomino -> domino ##
# #
# #
# # #
# # #
# #
#############################
p1 = pminos[1]
model.Add(p1[1][0] == p1[0][0])
model.Add(p1[1][1] == p1[0][1] + 1)
## 3rd polyomino -> pentomino ##
# #
# ## #
# ## #
# # #
# #
################################
p2 = pminos[2]
model.Add(p2[1][0] == p2[0][0] + 1)
model.Add(p2[2][0] == p2[0][0])
model.Add(p2[3][0] == p2[0][0] + 1)
model.Add(p2[4][0] == p2[0][0])
model.Add(p2[1][1] == p2[0][1])
model.Add(p2[2][1] == p2[0][1] + 1)
model.Add(p2[3][1] == p2[0][1] + 1)
model.Add(p2[4][1] == p2[0][1] + 2)
## 4th polyomino -> domino ##
# #
# #
# # #
# # #
# #
#############################
p3 = pminos[3]
model.Add(p3[1][0] == p3[0][0])
model.Add(p3[1][1] == p3[0][1] + 1)
## 5th polyomino -> monomino ##
# #
# #
# # #
# #
# #
###############################
#No constraints because 1 cell only
#No blocks can overlap:
block_addresses = []
n = 0
for p in pminos:
for c in p:
n += 1
block_address = model.NewIntVarFromDomain(cp_model.Domain.FromIntervals(ranges),'%i' % n)
model.Add(c[0] + c[1] * W == block_address)
block_addresses.append(block_address)
model.AddAllDifferent(block_addresses)
#Solve and print solutions as we find them
solver = cp_model.CpSolver()
solution_printer = SolutionPrinter(pminos)
status = solver.SearchForAllSolutions(model, solution_printer)
print('Status = %s' % solver.StatusName(status))
print('Number of solutions found: %i' % solution_printer.count)
class SolutionPrinter(cp_model.CpSolverSolutionCallback):
''' Print a solution. '''
def __init__(self, variables):
cp_model.CpSolverSolutionCallback.__init__(self)
self.variables = variables
self.count = 0
def on_solution_callback(self):
self.count += 1
plt.figure(figsize = (2, 2))
plt.grid(True)
plt.axis([0,W,H,0])
plt.yticks(np.arange(0, H, 1.0))
plt.xticks(np.arange(0, W, 1.0))
for i, p in enumerate(self.variables):
for c in p:
x = self.Value(c[0])
y = self.Value(c[1])
rect = plt.Rectangle((x, y), 1, 1, fc = cdict[i])
plt.gca().add_patch(rect)
for i in inactiveCells:
x = i%W
y = i//W
rect = plt.Rectangle((x, y), 1, 1, fc = 'None', hatch = '///')
plt.gca().add_patch(rect)
O problema é que eu codifiquei 5 poliaminoós únicos / fixos e não sei como definir as restrições para que cada padrão possível para cada polioino seja levado em consideração (desde que seja possível).
itertools
,numpy
,networkx
?minizinc
tag com uma resposta detalhada que aborda minha sugestão anterior sobre o usominizinc
.Respostas:
EDIT: Eu perdi a palavra "livre" na resposta original e dei resposta usando o OR-Tools para poliaminoinos fixos. Foi adicionada uma seção para responder a fim de incluir uma solução para poliaminoinos gratuitos - que o AFAICT se mostra bastante difícil de expressar com precisão na programação de restrições com o OR-Tools.
POLIOMINOS FIXOS COM FERRAMENTAS:
Sim, você pode fazer isso com programação de restrição no OR-Tools. O OR-Tools não sabe nada sobre geometria de grade 2D, portanto, é necessário codificar a geometria de cada forma que você possui em termos de restrições posicionais. Ou seja, uma forma é uma coleção de blocos / células que devem ter um certo relacionamento entre si, devem estar dentro dos limites da grade e não devem se sobrepor. Depois de ter seu modelo de restrição, basta solicitar ao CP-SAT Solver que o resolva, no seu caso, para todas as soluções possíveis.
Aqui está uma prova de conceito realmente simples, com duas formas de retângulo em uma grade 4x4 (você provavelmente também desejaria adicionar algum tipo de código de intérprete para passar das descrições de forma para um conjunto de variáveis e restrições do OR-Tools em um problema de maior escala) já que inserir as restrições manualmente é um pouco tedioso).
Dá:
POLIOMINOS LIVRES:
Se considerarmos a grade de células como um gráfico, o problema pode ser reinterpretado ao encontrar uma partição k das células da grade em que cada partição tem um tamanho específico e, além disso, cada partição é um componente conectado . Ou seja, não há diferença entre um componente conectado e um poliomino, e o restante desta resposta faz essa suposição.
A descoberta de todas as partições-k possíveis das células da grade em que cada partição possui um tamanho específico é bastante trivial para expressar na programação de restrições do OR-Tools. Mas a parte da conexão é difícil AFAICT (tentei e falhei por um bom tempo ...). Acho que a programação de restrições do OR-Tools não é a abordagem correta. Notei que a referência do OR-Tools C ++ para as bibliotecas de otimização de rede tem algumas coisas nos componentes conectados que podem valer uma olhada, mas não estou familiarizado com isso. Por outro lado, a solução de pesquisa recursiva ingênua em Python é bastante factível.
Aqui está uma solução ingênua "manual". É muito lento, mas é suportável para o seu caso 4x4. Os endereços são usados para identificar cada célula na grade. (Observe também que a página da wiki faz alusão a algo como esse algoritmo como uma solução ingênua e parece sugerir alguns mais eficientes para problemas similares de polioma).
Dá:
fonte
Uma maneira relativamente direta de restringir uma região simplesmente conectada no OR-Tools é restringir sua borda a ser um circuito . Se todos os seus polyominos tiverem tamanho menor que 8, não precisamos nos preocupar com os não conectados simplesmente.
Este código encontra todas as soluções 3884:
fonte
Para cada polyonomino e cada célula superior esquerda possível, você tem uma variável booleana que indica se essa célula é a parte superior esquerda do retângulo anexo.
Para cada célula e cada poliomino, você tem uma variável booleana que indica se essa célula está ocupada por esse poliomino.
Agora, para cada célula e cada poliomino, você tem uma série de implicações: a célula superior esquerda é selecionada implica que cada célula é realmente ocupada por esse poliomino.
Em seguida, as restrições: para cada célula, no máximo um polioino ocupa-o para cada polioino, existe exatamente uma célula que é sua parte superior esquerda.
este é um problema booleano puro.
fonte