Ich bin neu in der Welt der SAT-Löser und benötige eine Anleitung zum folgenden Problem.
Bedenkt, dass:
❶ Ich habe eine Auswahl von 14 benachbarten Zellen in einem 4 * 4-Raster
❷ Ich habe 5 Polyominoes (A, B, C, D, E) der Größen 4, 2, 5, 2 und 1
❸ Diese Polyominoes sind frei , dh ihre Form ist nicht festgelegt und kann unterschiedliche Muster bilden
Wie kann ich mit einem SAT-Solver alle möglichen Kombinationen dieser 5 freien Polyominoes innerhalb des ausgewählten Bereichs (Zellen in Grau) berechnen ?
Aus der aufschlussreichen Antwort von @ spinkus und der Dokumentation zu den OP-Tools konnte ich den folgenden Beispielcode erstellen (läuft in einem Jupyter-Notizbuch):
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)
Das Problem ist, dass ich 5 eindeutige / feste Polyominos fest codiert habe und nicht weiß, wie ich die Einschränkungen definieren soll, damit jedes mögliche Muster für jedes Polyomino berücksichtigt wird (sofern dies möglich ist).
itertools
,numpy
,networkx
?minizinc
mit einer detaillierten Antwort, die meinen vorherigen Vorschlag zur Verwendung abdecktminizinc
.Antworten:
BEARBEITEN: Ich habe das Wort "frei" in der ursprünglichen Antwort verpasst und mit OR-Tools für feste Polyominoes geantwortet. Ein zu beantwortender Abschnitt wurde hinzugefügt, um eine Lösung für freie Polyominoes aufzunehmen - was sich bei AFAICT als ziemlich schwierig herausstellt, um es in der Einschränkungsprogrammierung mit OR-Tools genau auszudrücken.
FESTE POLYOMINOES MIT OR-TOOLS:
Ja, Sie können dies mit der Einschränkungsprogrammierung in OR-Tools tun . OR-Tools weiß nichts über 2D-Gittergeometrie, daher müssen Sie die Geometrie jeder Form in Bezug auf Positionsbeschränkungen codieren. Das heißt, eine Form ist eine Sammlung von Blöcken / Zellen, die eine bestimmte Beziehung zueinander haben müssen, sich innerhalb der Grenzen des Gitters befinden müssen und sich nicht überlappen dürfen. Sobald Sie Ihr Constraint-Modell haben, bitten Sie den CP-SAT-Solver , es in Ihrem Fall für alle möglichen Lösungen zu lösen.
Hier ist ein wirklich einfacher Proof of Concept mit zwei Rechteckformen in einem 4x4-Raster (Sie möchten wahrscheinlich auch eine Art Interpreter-Code hinzufügen, um von Formbeschreibungen zu einer Reihe von OR-Tools-Variablen und -Beschränkungen in einem größeren Problem zu gelangen da die Eingabe der Einschränkungen von Hand etwas mühsam ist).
Gibt:
KOSTENLOSE POLYOMINOES:
Wenn wir das Gitter von Zellen als Diagramm betrachten, kann das Problem so interpretiert werden , dass eine k-Partition der Zellen des Gitters gefunden wird, wobei jede Partition eine bestimmte Größe hat und zusätzlich jede Partition eine verbundene Komponente ist . Dh AFAICT gibt es keinen Unterschied zwischen einer verbundenen Komponente und einem Polyomino, und der Rest dieser Antwort macht diese Annahme.
Das Auffinden aller möglichen "k-Partitionen der Zellen des Gitters, in denen jede Partition eine bestimmte Größe hat", ist in der OR-Tools-Einschränkungsprogrammierung ziemlich trivial auszudrücken. Aber der Teil der Verbundenheit ist schwer AFAICT (ich habe es eine ganze Weile versucht und bin gescheitert ...). Ich denke, OR-Tools Constraint-Programmierung ist nicht der richtige Ansatz. Ich habe festgestellt, dass die OR-Tools C ++ - Referenz für die Netzwerkoptimierungsbibliotheken einige Informationen zu verbundenen Komponenten enthält, die möglicherweise einen Blick wert sind, aber ich bin damit nicht vertraut. Andererseits ist eine naive rekursive Suchlösung in Python ziemlich machbar.
Hier ist eine "von Hand" naive Lösung. Es ist ziemlich langsam, aber für Ihren 4x4-Koffer erträglich. Adressen werden verwendet, um jede Zelle im Raster zu identifizieren. (Beachten Sie auch, dass die Wiki-Seite auf etwas wie diesen Algorithmus als naive Lösung anspielt und anscheinend einige effizientere für ähnliche Polyomino-Probleme vorschlägt).
Gibt:
quelle
Eine relativ einfache Möglichkeit, einen einfach verbundenen Bereich in OR-Tools einzuschränken, besteht darin, seine Grenze auf eine Schaltung zu beschränken . Wenn alle Ihre Polyominos eine Größe von weniger als 8 haben sollen, müssen wir uns nicht um nicht einfach verbundene kümmern.
Dieser Code findet alle 3884 Lösungen:
quelle
Für jedes Polyonomino und jede mögliche obere linke Zelle haben Sie eine boolesche Variable, die angibt, ob diese Zelle der obere linke Teil des umschließenden Rechtecks ist.
Für jede Zelle und jedes Polyomino haben Sie eine boolesche Variable, die angibt, ob diese Zelle von diesem Polyomino belegt ist.
Nun haben Sie für jede Zelle und jedes Polyomino eine Reihe von Implikationen: Die Auswahl der oberen linken Zelle impliziert, dass jede Zelle tatsächlich von diesem Polyomino besetzt ist.
Dann die Einschränkungen: Für jede Zelle belegt höchstens ein Polyomino es für jedes Polyomino, es gibt genau eine Zelle, die ihr oberer linker Teil ist.
Dies ist ein reines boolesches Problem.
quelle