Jestem nowy w świecie solverów SAT i potrzebuję wskazówek dotyczących następującego problemu.
Biorąc pod uwagę, że:
❶ Mam wybór 14 sąsiadujących komórek w siatce 4 * 4
❷ Mam 5 poliominoów (A, B, C, D, E) o rozmiarach 4, 2, 5, 2 i 1
Poly te poliominoes są wolne , tzn. Ich kształt nie jest ustalony i mogą tworzyć różne wzory
Jak obliczyć wszystkie możliwe kombinacje tych 5 wolnych poliominoów w wybranym obszarze (komórki w kolorze szarym) za pomocą solvera SAT?
Pożyczając zarówno z wnikliwej odpowiedzi @ spinkus, jak i dokumentacji narzędzi OR, mogłem utworzyć następujący przykładowy kod (działa w Notatniku Jupyter):
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)
Problem polega na tym, że mam zakodowane na stałe 5 unikalnych / stałych poliomino i nie wiem, jak zdefiniować ograniczenia, aby uwzględnić każdy możliwy wzór dla każdego poliomino (pod warunkiem, że jest to możliwe).
itertools
,numpy
,networkx
?minizinc
znaczniku o szczegółową odpowiedź, która obejmuje moje wcześniejsze sugestie na temat korzystaniaminizinc
.Odpowiedzi:
EDYCJA: Brakowało mi słowa „darmowy” w oryginalnej odpowiedzi i dałem odpowiedź za pomocą OR-Tools dla stałych poliomino. Dodano sekcję z odpowiedzią zawierającą rozwiązanie dla darmowych poliominoesów - które AFAICT okazuje się dość trudne do wyrażenia w programowaniu ograniczeń za pomocą OR-Tools.
STAŁE POLIOMINO Z OR-NARZĘDZIAMI:
Tak, możesz to zrobić z programowaniem ograniczeń w OR-Tools. OR-Tools nic nie wie o geometrii siatki 2D, więc musisz zakodować geometrię każdego kształtu pod względem ograniczeń pozycyjnych. Tj. Kształt to zbiór bloków / komórek, które muszą mieć ze sobą określoną relację, muszą znajdować się w granicach siatki i nie mogą się pokrywać. Gdy masz już model ograniczenia, po prostu poproś CP-SAT Solver o jego rozwiązanie we wszystkich możliwych rozwiązaniach.
Oto naprawdę prosty dowód koncepcji z dwoma prostokątnymi kształtami na siatce 4x4 (prawdopodobnie prawdopodobnie chciałbyś także dodać kod interpretera, aby przejść od opisów kształtów do zestawu zmiennych i ograniczeń OR-Tools w problemie na większą skalę ponieważ ręczne wprowadzanie ograniczeń jest nieco uciążliwe).
Daje:
DARMOWE POLIOMINO:
Jeśli weźmiemy pod uwagę siatkę komórek jako wykres, problem można ponownie zinterpretować jako znalezienie k-partycji komórek siatki, gdzie każda partycja ma określony rozmiar, a ponadto każda partycja jest połączonym komponentem . Tzn. AFAICT nie ma różnicy między połączonym komponentem a poliomino, a reszta tej odpowiedzi przyjmuje to założenie.
Znalezienie wszystkich możliwych „k-partycji komórek siatki, gdzie każda partycja ma określony rozmiar” jest dość trywialne do wyrażenia w programowaniu ograniczeń OR-Tools. Ale część związana z połączeniem jest trudna AFAICT (próbowałem i przez jakiś czas nie udawało mi się ...). Myślę, że programowanie ograniczeń OR-Tools nie jest właściwym podejściem. Zauważyłem, że odwołanie OR-Tools C ++ do bibliotek optymalizacji sieci zawiera pewne elementy na podłączonych komponentach, które mogą być warte obejrzenia, ale nie jestem z tym zaznajomiony. Z drugiej strony naiwne rozwiązanie wyszukiwania rekurencyjnego w Pythonie jest całkiem wykonalne.
Oto naiwne rozwiązanie „ręcznie”. Jest dość wolny, ale można go znieść w przypadku 4x4. Adresy służą do identyfikacji każdej komórki w siatce. (Zauważ też, że strona wiki nawiązuje do czegoś takiego jak ten algorytm jako naiwne rozwiązanie i wygląda na to, że sugeruje kilka bardziej wydajnych dla podobnych problemów z poliomino).
Daje:
źródło
Jednym stosunkowo prostym sposobem ograniczenia prostego połączenia regionu w OR-Tools jest ograniczenie jego granicy do obwodu . Jeśli wszystkie twoje poliaminy mają mieć rozmiar mniejszy niż 8, nie musimy się martwić o nie tylko połączone.
Ten kod znajduje wszystkie 3884 rozwiązania:
źródło
Dla każdej polionomino i każdej możliwej lewej górnej komórki masz zmienną logiczną, która wskazuje, czy ta komórka jest lewą górną częścią otaczającego prostokąta.
Dla każdej komórki i każdego poliomina masz zmienną boolowską, która wskazuje, czy komórka jest zajęta przez to poliomino.
Teraz, dla każdej komórki i każdego poliomino, masz szereg implikacji: wybrana jest lewa górna komórka oznacza, że każda komórka jest faktycznie zajęta przez to poliomino.
Następnie ograniczenia: dla każdej komórki, co najwyżej jedno poliomino zajmuje ją dla każdego poliomina, jest dokładnie jedna komórka, która jest jego górną lewą częścią.
jest to czysty problem boolowski.
źródło