Je suis nouveau dans le monde des solveurs SAT et j'aurais besoin de conseils concernant le problème suivant.
Étant donné que:
❶ J'ai une sélection de 14 cellules adjacentes dans une grille 4 * 4
❷ J'ai 5 polyominos (A, B, C, D, E) de tailles 4, 2, 5, 2 et 1
❸ ces polyominos sont libres , c'est-à-dire que leur forme n'est pas fixe et peut former des motifs différents
Comment puis-je calculer toutes les combinaisons possibles de ces 5 polyominos libres à l'intérieur de la zone sélectionnée (cellules en gris) avec un solveur SAT?
En empruntant à la fois à la réponse perspicace de @ spinkus et à la documentation des outils OR, je pourrais créer l'exemple de code suivant (exécuté dans un bloc-notes 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)
Le problème est que j'ai codé en dur 5 polyominos uniques / fixes et je ne sais pas comment définir les contraintes afin que chaque modèle possible pour chaque polyomino soit pris en compte (à condition que cela soit possible).
itertools
,numpy
,networkx
?minizinc
balise avec une réponse détaillée qui couvre ma suggestion précédente sur l'utilisationminizinc
.Réponses:
EDIT: J'ai raté le mot "gratuit" dans la réponse originale et ai donné la réponse en utilisant OR-Tools pour les polyominos fixes. Ajout d'une section pour répondre afin d'inclure une solution pour les polyominos libres - qu'AFAICT s'avère assez difficile à exprimer précisément dans la programmation par contraintes avec OR-Tools.
POLYOMINOS FIXES AVEC OUTILS OU:
Oui, vous pouvez le faire avec la programmation par contraintes dans OR-Tools. OR-Tools ne sait rien de la géométrie de la grille 2D, vous devez donc encoder la géométrie de chaque forme que vous avez en termes de contraintes de position. C'est-à-dire qu'une forme est une collection de blocs / cellules qui doivent avoir une certaine relation les uns avec les autres, doivent être dans les limites de la grille et ne doivent pas se chevaucher. Une fois que vous avez votre modèle de contrainte, il vous suffit de demander au solveur CP-SAT de le résoudre, dans votre cas, pour toutes les solutions possibles.
Voici une preuve de concept très simple avec deux formes rectangulaires sur une grille 4x4 (vous voudrez probablement aussi ajouter une sorte de code d'interpréteur pour passer des descriptions de formes à un ensemble de variables et de contraintes OR-Tools dans un problème à plus grande échelle car la saisie manuelle des contraintes est un peu fastidieuse).
Donne:
POLYOMINOES GRATUITS:
Si nous considérons la grille de cellules comme un graphique, le problème peut être réinterprété comme trouvant une k-partition des cellules de la grille où chaque partition a une taille spécifique et en plus chaque partition est un composant connecté . C'est-à-dire AFAICT, il n'y a pas de différence entre un composant connecté et un polyomino et le reste de cette réponse fait cette hypothèse.
La recherche de toutes les "k-partitions des cellules de la grille où chaque partition a une taille spécifique" est assez banale à exprimer dans la programmation des contraintes OR-Tools. Mais la partie connectivité est difficile AFAICT (j'ai essayé et échoué pendant un bon moment ...). Je pense que la programmation par contraintes OR-Tools n'est pas la bonne approche. J'ai remarqué que la référence OR-Tools C ++ pour les bibliothèques d'optimisation de réseau contient des informations sur les composants connectés qui pourraient valoir le coup d'œil, mais je ne les connais pas. D'un autre côté, la solution de recherche récursive naïve en Python est assez faisable.
Voici une solution naïve "à la main". C'est assez lent mais supportable pour votre boîtier 4x4. Les adresses sont utilisées pour identifier chaque cellule de la grille. (Notez également que la page wiki fait allusion à quelque chose comme cet algorithme en tant que solution naïve et semble suggérer des solutions plus efficaces pour des problèmes de polyomino similaires).
Donne:
la source
Une façon relativement simple de contraindre une région simplement connectée dans OR-Tools est de contraindre sa frontière à être un circuit . Si tous vos polyominos doivent avoir une taille inférieure à 8, nous n'avons pas à nous soucier des non-connectés.
Ce code trouve toutes les 3884 solutions:
la source
Pour chaque polyonomino et chaque cellule supérieure gauche possible, vous avez une variable booléenne qui indique si cette cellule est la partie supérieure gauche du rectangle englobant.
Pour chaque cellule et chaque polyomino, vous avez une variable booléenne qui indique si cette cellule est occupée par ce polyomino.
Maintenant, pour chaque cellule et chaque polyomino, vous avez une série d'implications: la cellule supérieure gauche est sélectionnée implique que chaque cellule est réellement occupée par ce polyomino.
Ensuite les contraintes: pour chaque cellule, au plus un polyomino l'occupe pour chaque polyomino, il y a exactement une cellule qui est sa partie supérieure gauche.
c'est un pur problème booléen.
la source