私はSATソルバーの世界に不慣れで、次の問題についていくつかのガイダンスが必要になります。
それを考慮して:
❶4×4のグリッドに隣接する14のセルを選択している
❷私は5 polyominoes (A、B、C、D、E)のサイズ4、2、5、2、1を持っています
❸これらのポリオミノはfreeです。つまり、その形状は固定されておらず、さまざまなパターンを形成できます。
SATソルバーを使用して、選択した領域(灰色のセル)内でこれら5つの遊離ポリオミノの可能なすべての組み合わせを計算するにはどうすればよいですか?
@spinkusの洞察に満ちた答えとORツールのドキュメントの両方を借りて、次のサンプルコードを作成できます(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)
問題は、5つの一意の/固定されたポリオミノをハードコードしていて、どうすればよいかわかりません各ポリオミノの可能な各パターンが考慮されるように制約を定義する方法(可能な場合) 。
OR-Toolsで単純に接続された領域を制約する比較的簡単な方法の1つは、その境界を circuit になるように制約することです。すべてのポリオミノのサイズが8未満である場合、単純に接続されていないものについて心配する必要はありません。
このコードは、すべての3884ソリューションを検出します。
from ortools.sat.python import cp_model
cells = {(x, y) for x in range(4) for y in range(4) if x > 1 or y > 0}
sizes = [4, 2, 5, 2, 1]
num_polyominos = len(sizes)
model = cp_model.CpModel()
# Each cell is a member of one polyomino
member = {
(cell, p): model.NewBoolVar(f"member{cell, p}")
for cell in cells
for p in range(num_polyominos)
}
for cell in cells:
model.Add(sum(member[cell, p] for p in range(num_polyominos)) == 1)
# Each polyomino contains the given number of cells
for p, size in enumerate(sizes):
model.Add(sum(member[cell, p] for cell in cells) == size)
# Find the border of each polyomino
vertices = {
v: i
for i, v in enumerate(
{(x + i, y + j) for x, y in cells for i in [0, 1] for j in [0, 1]}
)
}
edges = [
Edge
for x, y in cells
for Edge in [
((x, y), (x + 1, y)),
((x + 1, y), (x + 1, y + 1)),
((x + 1, y + 1), (x, y + 1)),
((x, y + 1), (x, y)),
]
]
border = {
(Edge, p): model.NewBoolVar(f"border{Edge, p}")
for Edge in edges
for p in range(num_polyominos)
}
for (((x0, y0), (x1, y1)), p), border_var in border.items():
left_cell = ((x0 + x1 + y0 - y1) // 2, (y0 + y1 - x0 + x1) // 2)
right_cell = ((x0 + x1 - y0 + y1) // 2, (y0 + y1 + x0 - x1) // 2)
left_var = member[left_cell, p]
model.AddBoolOr([border_var.Not(), left_var])
if (right_cell, p) in member:
right_var = member[right_cell, p]
model.AddBoolOr([border_var.Not(), right_var.Not()])
model.AddBoolOr([border_var, left_var.Not(), right_var])
else:
model.AddBoolOr([border_var, left_var.Not()])
# Each border is a circuit
for p in range(num_polyominos):
model.AddCircuit(
[(vertices[v0], vertices[v1], border[(v0, v1), p]) for v0, v1 in edges]
+ [(i, i, model.NewBoolVar(f"vertex_loop{v, p}")) for v, i in vertices.items()]
)
# Print all solutions
x_range = range(min(x for x, y in cells), max(x for x, y in cells) + 1)
y_range = range(min(y for x, y in cells), max(y for x, y in cells) + 1)
solutions = 0
class SolutionPrinter(cp_model.CpSolverSolutionCallback):
def OnSolutionCallback(self):
global solutions
solutions += 1
for y in y_range:
print(
*(
next(
p
for p in range(num_polyominos)
if self.Value(member[(x, y), p])
)
if (x, y) in cells
else "-"
for x in x_range
)
)
print()
solver = cp_model.CpSolver()
solver.SearchForAllSolutions(model, SolutionPrinter())
print("Number of solutions found:", solutions)
各ポリオノミノ、および考えられる左上のセルごとに、このセルが囲んでいる長方形の左上の部分かどうかを示すブール変数があります。
各セルと各ポリオミノについて、このセルがこのポリオミノによって占められているかどうかを示すブール変数があります。
これで、各セルと各ポリオミノについて、一連の影響があります。左上のセルが選択されているということは、各セルがこのポリオミノによって実際に占有されているということです。
次に、制約:各セルについて、多くても1つのポリオミノが各ポリオミノについてそれを占有します。その左上の部分であるセルは1つだけです。
これは純粋なブール問題です。