Example #1
0
                            ###
                            # 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()
Example #2
0
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