### # On génère le domaines des variables de case ### # Les domaines sont restreints aux formes différentes de celles placées dans un coin VAR_CELLS = dict() for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: VAR_CELLS[cell] = set(FREE_PENTOMINOS) VAR_CELLS[cell].remove(shape_top_left) VAR_CELLS[cell].remove(shape_top_right) P = constraint_program({ **copy.deepcopy(VAR_CELLS), **copy.deepcopy(VAR_SHAPES) }) P.set_arc_consistency() ### # Ajout des contraintes ### # Ces contraintes sont identiques à celles de l'approche naïve # sauf qu'elles ne portent pas sur les formes que l'on a fixé. for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: for shape_current, quintuplets_current in VAR_SHAPES.items( ): setquint = set()
def solve_top_right(quintuplet_top_right): count_local = 0 if N - 1 in quintuplet_top_right: VAR_SHAPES = copy.deepcopy(SHAPES) del VAR_SHAPES[shape_top_left] del VAR_SHAPES[shape_top_right] for shape_current, quintuplets_current in VAR_SHAPES.items(): _remove = set() for quintuplet_current in quintuplets_current: if set(quintuplet_current).intersection(set(quintuplet_top_left)) \ or set(quintuplet_current).intersection(quintuplet_top_right): _remove.add(quintuplet_current) VAR_SHAPES[shape_current].difference_update(_remove) VAR_CELLS = dict() for cell in range(N * M): VAR_CELLS[cell] = set(FREE_PENTOMINOS) VAR_CELLS[cell].remove(shape_top_left) VAR_CELLS[cell].remove(shape_top_right) for cell in quintuplet_top_left: del VAR_CELLS[cell] for cell in quintuplet_top_right: del VAR_CELLS[cell] P = constraint_program({ **copy.deepcopy(VAR_CELLS), **copy.deepcopy(VAR_SHAPES) }) P.set_arc_consistency() for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: for shape_current, quintuplets_current in VAR_SHAPES.items(): setquint = set() for quintuplet_current in quintuplets_current: if cell in quintuplet_current and shape_current in VAR_CELLS[ cell]: setquint.add((shape_current, quintuplet_current)) for othershape in VAR_CELLS[cell]: for otherquintuplet in quintuplets_current: if othershape != shape_current and cell not in otherquintuplet: setquint.add((othershape, otherquintuplet)) if setquint: P.add_constraint(cell, shape_current, setquint) print('Solving {} {} + {} {}...'.format(shape_top_left, quintuplet_top_left, shape_top_right, quintuplet_top_right)) t2 = time.time() for sol in P.solve_all(): count_local += 1 print('Time for sol. n˚{} : {} -- Total: {}'.format( count + count_local, time.time() - t2, time.time() - t)) for i in quintuplet_top_left: sol[i] = shape_top_left for i in quintuplet_top_right: sol[i] = shape_top_right sol[shape_top_left] = quintuplet_top_left sol[shape_top_right] = quintuplet_top_right print_sol(sol) t2 = time.time() # print('Solved {0} in {1}.'.format(f, time.time() - t)) del P return count_local
def main(argv=[]): """ Fonction principale : créer le CSP, ses variables, ses contraintes et le résout à l'aide du solveur dédié """ ### # On génère le domaines des variables de forme ### shapes = { 'F': [[0, 1, 1], [1, 1, 0], [0, 1, 0]], 'I': [[1, 1, 1, 1, 1]], 'L': [[1, 1, 1, 1], [1, 0, 0, 0]], 'N': [[1, 1, 1, 0], [0, 0, 1, 1]], 'P': [[1, 1, 1], [0, 1, 1]], 'T': [[1, 0, 0], [1, 1, 1], [1, 0, 0]], 'U': [[1, 0, 1], [1, 1, 1]], 'V': [[1, 1, 1], [1, 0, 0], [1, 0, 0]], 'W': [[1, 1, 0], [0, 1, 1], [0, 0, 1]], 'X': [[0, 1, 0], [1, 1, 1], [0, 1, 0]], 'Y': [[1, 0], [1, 1], [1, 0], [1, 0]], 'Z': [[1, 0, 0], [1, 1, 1], [0, 0, 1]] } # Dictionnaire auxiliaire, clé = forme (F, I, L, etc), valeur = configurations (sous la forme d'une matrice all_shapes = dict() # Dictionnaire clé = formes, valeurs = quintuplets de cases possibles (sous la forme de tuples) SHAPES = dict() # formes possibles FREE_PENTOMINOS = [ "F", "I", "L", "N", "P", "T", "U", "V", "W", "X", "Y", "Z" ] # Génère tous les pentominos possibles (rotations, symétries) # Les divers changements de types sont nécessaires car un tuple n'est pas mutable et un set n'est pas hashable for shape_name, shape_matrix in shapes.items(): shape_set = set() for modified_shape in get_all_shapes(shape_matrix[:]): shape_set.add(tuple(modified_shape)) all_shapes[shape_name] = list( map(list, [map(list, line) for line in shape_set])) # A partir des configurations, on génère les quintuplets possibles par translation for shape in all_shapes: free_shapes = all_shapes[shape] translated = [] for fs in free_shapes: translated += possibles(fs) SHAPES[shape] = set(translated) ### # On génère le domaines des variables de case ### CELL = dict() for cell in range(N * M): CELL[cell] = set() for shape, quintuplets in SHAPES.items(): for quintuplet in quintuplets: for cell in quintuplet: CELL[cell].update({shape}) ### # Création du CSP ### P = constraint_program({**CELL, **SHAPES}) P.set_arc_consistency() ### # Ajout des contraintes ### for cell in range(N * M): for shape, quintuplets in SHAPES.items(): setquint = set() for quintuplet in quintuplets: # Soit la cellule est dans la forme et la forme contient la cellule if cell in quintuplet and shape in CELL[cell]: setquint.add((shape, quintuplet)) for othershape in CELL[cell]: for otherquintuplet in quintuplets: # Soit la cellule n'est pas dans la forme et la forme ne contient pas la cellule if othershape != shape and cell not in otherquintuplet: setquint.add((othershape, otherquintuplet)) # mise a jour if setquint: P.add_constraint(cell, shape, setquint) # compteur de solution count = 0 # pour n'afficher qu'une seule solution print_one = True print('Solving {}x{}...'.format(M, N)) # On enregistre le temps d'exécution t = time.time() for sol in P.solve_all(): count += 1 if print_one: print('Time for first solution: {:.2f}'.format(time.time() - t)) print_sol(sol) print_one = False print(count // 4) print('Solved in a total time of: {:.2f}s'.format(time.time() - t))
def main(argv=[]): # coins corners = set([0, N - 1, (M - 1) * N, N * M - 1]) ### # On génère le domaines des variables de forme ### shapes = { 'F': [[0, 1, 1], [1, 1, 0], [0, 1, 0]], 'I': [[1, 1, 1, 1, 1]], 'L': [[1, 1, 1, 1], [1, 0, 0, 0]], 'N': [[1, 1, 1, 0], [0, 0, 1, 1]], 'P': [[1, 1, 1], [0, 1, 1]], 'T': [[1, 0, 0], [1, 1, 1], [1, 0, 0]], 'U': [[1, 0, 1], [1, 1, 1]], 'V': [[1, 1, 1], [1, 0, 0], [1, 0, 0]], 'W': [[1, 1, 0], [0, 1, 1], [0, 0, 1]], 'X': [[0, 1, 0], [1, 1, 1], [0, 1, 0]], 'Y': [[1, 0], [1, 1], [1, 0], [1, 0]], 'Z': [[1, 0, 0], [1, 1, 1], [0, 0, 1]] } all_shapes = dict() SHAPES = dict() # Dans le cas, il ny'a pas de formes qui peuvent recouvrir à elle seule un bord, on peut prendre une configuration # plus efficace if N == 6 or M == 6: FREE_PENTOMINOS = [ "F", "I", "L", "N", "P", "T", "U", "V", "W", "X", "Y", "Z" ] else: FREE_PENTOMINOS = [ "U", "V", "X", "Z", "N", "P", "T", "W", "F", "Y", "L", "I" ] # formes possibles for shape_name, shape_matrix in shapes.items(): shape_set = set() for modified_shape in get_all_shapes(shape_matrix[:]): shape_set.add(tuple(modified_shape)) all_shapes[shape_name] = list( map(list, [map(list, line) for line in shape_set])) for shape in all_shapes: free_shapes = all_shapes[shape] translated = [] for fs in free_shapes: translated += possibles(fs) SHAPES[shape] = set(translated) count = 0 t = time.time() print_one = True # on sélectionne une forme à placer dans le coin en haut à gauche for index_top_left, shape_top_left in enumerate(FREE_PENTOMINOS): quintuplets_top_left = SHAPES[shape_top_left] # on sélectionne une forme à placer dans le coin en haut à droite for index_top_right in range(index_top_left + 1, len(FREE_PENTOMINOS)): shape_top_right = FREE_PENTOMINOS[index_top_right] quintuplets_top_right = SHAPES[shape_top_right] # on récupère uniquement les possibilités où la forme est effectivement en haut à gauche for quintuplet_top_left in quintuplets_top_left: if 0 in quintuplet_top_left: # on récupère uniquement les possibilités où la forme est effectivement en haut à droite for quintuplet_top_right in quintuplets_top_right: if N - 1 in quintuplet_top_right: ### # On met à jour le domaines des variables de forme ### # on récupère les autres formes VAR_SHAPES = copy.deepcopy(SHAPES) del VAR_SHAPES[shape_top_left] del VAR_SHAPES[shape_top_right] # on supprime des autres formes les possibilités qui intersectent une des # deux forme placée dans un coin for shape_current, quintuplets_current in VAR_SHAPES.items( ): _remove = set() for quintuplet_current in quintuplets_current: if set(quintuplet_current).intersection(set(quintuplet_top_left)) \ or set(quintuplet_current).intersection(quintuplet_top_right): _remove.add(quintuplet_current) VAR_SHAPES[shape_current].difference_update( _remove) ### # On génère le domaines des variables de case ### # Les domaines sont restreints aux formes différentes de celles placées dans un coin VAR_CELLS = dict() for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: VAR_CELLS[cell] = set(FREE_PENTOMINOS) VAR_CELLS[cell].remove(shape_top_left) VAR_CELLS[cell].remove(shape_top_right) P = constraint_program({ **copy.deepcopy(VAR_CELLS), **copy.deepcopy(VAR_SHAPES) }) P.set_arc_consistency() ### # Ajout des contraintes ### # Ces contraintes sont identiques à celles de l'approche naïve # sauf qu'elles ne portent pas sur les formes que l'on a fixé. for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: for shape_current, quintuplets_current in VAR_SHAPES.items( ): setquint = set() for quintuplet_current in quintuplets_current: if cell in quintuplet_current and shape_current in VAR_CELLS[ cell]: setquint.add( (shape_current, quintuplet_current)) for othershape in VAR_CELLS[cell]: for otherquintuplet in quintuplets_current: if othershape != shape_current and cell not in otherquintuplet: setquint.add( (othershape, otherquintuplet)) if setquint: P.add_constraint( cell, shape_current, setquint) for sol in P.solve_all(): # Si toute la colonne de gauche ET toute la colonne de droite sont recouvertes par # une forme unique, on sait que l'on va retrouver ces solutions symétrisées. # NB: en pratique cela ne se produit QUE pour le cas 3x20, qui est un cas avec # tellement peu de solutions que cette méthode est solution : il ne sert à rien de # modifier le solveur pour 2 solutions. left = set(range(0, N * M, N)) right = set(range(N - 1, N * M, N)) if left.issubset( set(quintuplet_top_left )) and right.issubset( set(quintuplet_top_right)): count += 0.5 count += 1 # On rajoute à la solution les deux formes que l'on a fixé for i in quintuplet_top_left: sol[i] = shape_top_left for i in quintuplet_top_right: sol[i] = shape_top_right sol[shape_top_left] = quintuplet_top_left sol[shape_top_right] = quintuplet_top_right if print_one: print('Time for first solution: {:.2f}'. format(time.time() - t)) print_sol(sol) print_one = False del P ### # Suppression des symétries ### # On enlève toutes les positions de la forme considérée qui couvrent un angle to_remove = set() for quintuplet_top_left in quintuplets_top_left: if set(quintuplet_top_left).intersection(corners): to_remove.add(quintuplet_top_left) SHAPES[shape_top_left].difference_update(to_remove) print(count) print('Solved in a total time of: {:.2f}s'.format(time.time() - t))
def solve_top_right(quintuplet_top_right): """ Cette fonction est lancée dans le main. Elle vise à retourner le nombre de solutions possibles d'une configuration telle que la forme en haut à gauche et la forme en haut à droite est fixée. """ count_local = 0 # Compteur des solutions dans cette configuration particulière if N - 1 in quintuplet_top_right: VAR_SHAPES = copy.deepcopy(SHAPES) # Variables correspondant aux formes qui va être utilisée dans le solveur # On sait déjà quelles seront les formes en haut à droite et en haut à gauche, # on ne les considérera donc pas dans le solveur. del VAR_SHAPES[shape_top_left] del VAR_SHAPES[shape_top_right] # On enlève du domaine des autres variables formes tous les dispositions # qui intersectent les formes que nous avons fixées for shape_current, quintuplets_current in VAR_SHAPES.items(): _remove = set() for quintuplet_current in quintuplets_current: if set(quintuplet_current).intersection(set(quintuplet_top_left)) \ or set(quintuplet_current).intersection(quintuplet_top_right): _remove.add(quintuplet_current) VAR_SHAPES[shape_current].difference_update(_remove) # On crée le domaine des variables cases. # La case ne peut pas être de la même forme que le pentomino en haut à gauche et en haut à droite. VAR_CELLS = dict() for cell in range(N * M): VAR_CELLS[cell] = set(FREE_PENTOMINOS) VAR_CELLS[cell].remove(shape_top_left) VAR_CELLS[cell].remove(shape_top_right) # On sait déjà quelle est la forme dans les cases en haut à droite et à gauche, # on ne les considérera donc pas dans le solveur. for cell in quintuplet_top_left: del VAR_CELLS[cell] for cell in quintuplet_top_right: del VAR_CELLS[cell] # On crée le solveur de contraintes P = constraint_program({**copy.deepcopy(VAR_CELLS), **copy.deepcopy(VAR_SHAPES)}) P.set_arc_consistency() # Le bloc suivant ajoute les contraintes au solveur for cell in range(N * M): if cell not in quintuplet_top_left and cell not in quintuplet_top_right: for shape_current, quintuplets_current in VAR_SHAPES.items(): setquint = set() for quintuplet_current in quintuplets_current: if cell in quintuplet_current and shape_current in VAR_CELLS[cell]: # Si la cellule C étudiée est recouverte par la forme F, # alors F est un quintuplet qui doit contenir C setquint.add((shape_current, quintuplet_current)) for othershape in VAR_CELLS[cell]: for otherquintuplet in quintuplets_current: if othershape != shape_current and cell not in otherquintuplet: # Si la cellule C étudiée n'est pas recouverte par la forme F, # alors F est un quintuplet qui ne doit pas contenir C. setquint.add((othershape, otherquintuplet)) if setquint: P.add_constraint(cell, shape_current, setquint) for sol in P.solve_all(): # Pour chaque solution count_local += 1 # On compte une solution en plus # On rajoute les formes fixées à la solution for i in quintuplet_top_left: sol[i] = shape_top_left for i in quintuplet_top_right: sol[i] = shape_top_right sol[shape_top_left] = quintuplet_top_left sol[shape_top_right] = quintuplet_top_right print_sol(sol) del P # Si toute la colonne de gauche ET toute la colonne de droite sont recouvertes par une forme unique, on sait que # l'on va retrouver ces solutions symétrisées. # NB: en pratique cela ne se produit QUE pour le cas 3x20, qui est un cas avec tellement peu de solutions que # cette méthode est solution : il ne sert à rien de modifier le solveur pour 2 solutions. left = set(range(0, N*M, N)) right = set(range(N-1, N*M, N)) if left.issubset(set(quintuplet_top_left)) and right.issubset(set(quintuplet_top_right)): count_local /= 2 return count_local