Soy nuevo en el mundo de los solucionadores de SAT y necesitaría alguna orientación sobre el siguiente problema.
Teniendo en cuenta que:
❶ Tengo una selección de 14 celdas adyacentes en una cuadrícula de 4 * 4
❷ Tengo 5 poliominoes (A, B, C, D, E) de tamaños 4, 2, 5, 2 y 1
❸ estos poliominós son libres , es decir, su forma no es fija y pueden formar diferentes patrones
¿Cómo puedo calcular todas las combinaciones posibles de estos 5 poliominós libres dentro del área seleccionada (celdas en gris) con un solucionador SAT?
Tomando prestado tanto de la respuesta perspicaz de @ spinkus como de la documentación de las herramientas OR, podría hacer el siguiente código de ejemplo (se ejecuta en un 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)
El problema es que he codificado 5 polyominoes únicos / fijos y no sé cómo definir las restricciones para tener en cuenta cada posible patrón para cada poliomino (siempre que sea posible).
itertools
,numpy
,networkx
?minizinc
etiqueta con una respuesta detallada que cubre mi sugerencia anterior sobre el usominizinc
.Respuestas:
EDITAR: Me perdí la palabra "gratis" en la respuesta original y di respuesta usando las herramientas OR para poliominoes fijos. Se agregó una sección para responder para incluir una solución para poliominoes libres, que AFAICT resulta ser bastante difícil de expresar con precisión en la programación de restricciones con OR-Tools.
POLIOMINOES FIJOS CON HERRAMIENTAS OR:
Sí, puedes hacerlo con programación restringida en OR-Tools. OR-Tools no sabe nada sobre la geometría de cuadrícula 2D, por lo que debe codificar la geometría de cada forma que tenga en términos de restricciones de posición. Es decir, una forma es una colección de bloques / celdas que deben tener una cierta relación entre sí, deben estar dentro de los límites de la cuadrícula y no deben superponerse. Una vez que tenga su modelo de restricción, simplemente solicite al CP-SAT Solver que lo resuelva, en su caso, para todas las soluciones posibles.
Aquí hay una prueba de concepto realmente simple con dos formas rectangulares en una cuadrícula de 4x4 (probablemente también desee agregar algún tipo de código de intérprete para pasar de las descripciones de forma a un conjunto de variables y restricciones de herramientas OR en un problema de mayor escala ya que ingresar las restricciones a mano es un poco tedioso).
Da:
POLIOMINOS GRATIS:
Si consideramos la cuadrícula de celdas como un gráfico, el problema se puede reinterpretar como encontrar una partición k de las celdas de la cuadrícula donde cada partición tiene un tamaño específico y, además, cada partición es un componente conectado . Es decir, AFAICT no hay diferencia entre un componente conectado y un poliomino y el resto de esta respuesta hace esa suposición.
Encontrar todas las posibles "particiones k de las celdas de la cuadrícula donde cada partición tiene un tamaño específico" es bastante trivial de expresar en la programación de restricciones de herramientas OR. Pero la parte de conectividad es difícil AFAICT (lo intenté y fallé durante bastante tiempo ...). Creo que la programación de restricciones de OR-Tools no es el enfoque correcto. Noté que la referencia de OR-Tools C ++ para las bibliotecas de optimización de red tiene algunas cosas sobre los componentes conectados que pueden valer la pena, pero no estoy familiarizado con eso. Por otro lado, la solución de búsqueda recursiva ingenua en Python es bastante factible.
Aquí hay una solución ingenua "a mano". Es bastante lento, pero es soportable para su estuche 4x4. Las direcciones se utilizan para identificar cada celda en la cuadrícula. (También tenga en cuenta que la página wiki alude a algo como este algoritmo como una solución ingenua y parece que sugiere algunos más eficientes para problemas de poliomino similares).
Da:
fuente
Una forma relativamente sencilla de restringir una región simplemente conectada en OR-Tools es restringir su borde para que sea un circuito . Si todos sus poliominos tienen un tamaño inferior a 8, no debemos preocuparnos por los que no están simplemente conectados.
Este código encuentra todas las soluciones 3884:
fuente
Para cada polyonomino, y cada posible celda superior izquierda, tiene una variable booleana que indica si esta celda es la parte superior izquierda del rectángulo delimitador.
Para cada celda y cada poliomino, tiene una variable booleana que indica si esta celda está ocupada por este poliomino.
Ahora, para cada celda y cada poliomino, tiene una serie de implicaciones: la celda superior izquierda seleccionada implica que cada celda está realmente ocupada por este poliomino.
Luego las restricciones: para cada celda, como máximo un poliomino lo ocupa para cada poliomino, hay exactamente una celda que es su parte superior izquierda.
Este es un problema booleano puro.
fuente