Example #1
0
def gen_contiguous_constraints_single(index_, count_, *, width, height, board, puzzle):
    at_ = lambda l, c : board[l][c]
    at_puzzle = lambda l, c : puzzle[l][c]
    def is_valid_cell(ind, current_index=index_, height=height, width=width):
        if ind == current_index: # redundant check at_puzzle(ind) == count_
            return True
        if not inside_board(ind, height=height, width=width):
            return False
        # a cell is considered valid neighbour (to spawn) if it is empty
        # or it has the same count_
        return at_puzzle(*ind) == 0 or at_puzzle(*ind) == count_
    possibs = [] # possible configurations given the count_ of contiguous cells in the block
    blocks = get_block(index_, nb_cells=count_, is_valid_cell=is_valid_cell)
    for block in blocks:
        block_vars = [ at_(*ind) for ind in block ]
        # fence can be thought of as completely encircling the block
        fence = get_fence(enclosure=block)
        geom = { 'width': width, 'height': height }
        fence_inside_board = [ ind for ind in fence
                if inside_board(ind, **geom) ]
        fence_var = [ at_(*ind) for ind in fence_inside_board ]
        fenced_enclosure = And(
                coerce_eq(block_vars, [count_] * len(block_vars)),
                And([var != count_ for var in fence_var]))
        possibs.append(simplify(fenced_enclosure))
    return Exactly(*possibs, 1) #only one configuration would prevail
 def gen_consecutive_nums_constraint(l, c):
     val_neighs = [ neigh for neigh in neighbours(l, c)
             if inside_board(neigh, height=order, width=order) ]
     neighs = get_vars_at(val_neighs)
     occupied_neighs = set(neighs).intersection(occupied_cells)
     var = at_(l, c) #content of current cell
     one_adj_cell_is_consec_c = Exactly(*[ adj == var + 1 for adj in occupied_neighs ], 1)
     current_cell_is_max_c = var == maximum
     return Or(current_cell_is_max_c, one_adj_cell_is_consec_c)
Example #3
0
 def gen_coupling_constraints(l, c):
     val_neighs = [
         neigh for neigh in neighbours((l, c))
         if inside_board(neigh, height=height, width=width)
     ]
     couplings = [X[l][c] == -at_(*cell) for cell in val_neighs]
     # we don't add constraint that it is unoccupied by a tree
     # it's taken care by another set of constraints
     # return Exactly(*couplings, 1)
     return Exactly(*couplings, 1)
    def gen_consecutive_nums_constraint(l, c):
        geom = {'height': height, 'width': width}
        val_neighs = [
            neigh for neigh in neighbours((l, c))
            if inside_board(neigh, **geom)
        ]
        neighs = get_vars_at(val_neighs)

        var = at_(l, c)  #content of current cell
        one_adj_cell_is_consec_c = Exactly(*[adj == var + 1 for adj in neighs],
                                           1)
        current_cell_is_max_c = var == MAX
        unoccupied_c = var == 0
        return Or(unoccupied_c, current_cell_is_max_c,
                  one_adj_cell_is_consec_c)
def solve_puzzle_shikaku(puzzle, *, height, width):
    X = IntMatrix('r', nb_rows=height, nb_cols=width)

    assert sum(
        flatten(puzzle)
    ) == height * width, "Sum of the areas of all rectangles must be equal to board area"

    at_ = lambda l, c: X[l][c]

    def get_vars_at(indices):
        return [at_(*ind) for ind in indices]

    def get_unique_rect_id(l, c):
        return l * width + c + 1

    rectangle_area_c = []

    for l, c in itertools.product(range(height), range(width)):
        rect_area = puzzle[l][c]
        if rect_area > 0:
            rect_id = get_unique_rect_id(l, c)
            rects = get_possible_rectangles_at((l, c),
                                               height=height,
                                               width=width,
                                               rect_area=rect_area)
            rectangle_configs_with_this_id = []
            for rect in rects:
                indices = list(get_indices_in_rect(rect))
                rect_vars = get_vars_at(indices)
                # This is like "coloring" the rectangle with id
                cnstrnt = coerce_eq(rect_vars, [rect_id] * len(rect_vars))
                rectangle_configs_with_this_id.append(cnstrnt)
            rectangle_area_c.append(Exactly(*rectangle_configs_with_this_id,
                                            1))

    s = Solver()

    s.add(rectangle_area_c)

    s.check()
    m = s.model()
    return [[m[cell] for cell in row] for row in X]
def gen_contiguous_constraints_single(index_, count_, *, width, height, board):
    at_ = lambda l, c: board[l][c]
    # count_ - 1 because the current square is always counted
    possibs = []  #possible configurations given the count_ of white squares
    for distrib in distribute_in_4_directions(count_ - 1):
        strict_boundaries = boundaries(index_, spawn_direction=distrib)
        geom = {'width': width, 'height': height}
        can_spawn = all(
            (inside_board(ind, **geom) for ind in strict_boundaries))
        if can_spawn:
            enclosure = get_enclosed_space(index_, spawn_direction=distrib)
            encloure_vars = [at_(*ind) for ind in enclosure]
            direction_plus_one = tuple(d + 1 for d in distrib)
            # walls can be thought of as surrounding boundaries
            walls = boundaries(index_, spawn_direction=direction_plus_one)
            walls_within_board = [
                ind for ind in walls if inside_board(ind, **geom)
            ]
            walls_var = [at_(*ind) for ind in walls_within_board]
            walled_enclosure = And(
                coerce_eq(encloure_vars, [WHITE] * len(encloure_vars)),
                coerce_eq(walls_var, [BLACK] * len(walls_var)))
            possibs.append(simplify(walled_enclosure))
    return Exactly(*possibs, 1)  #only one configuration would prevail
 def gen_count_lightbulb_constraint(l, c, count_lightbulbs):
     neigh_as_light_bulb = [
         at_(*cell) == LIGHT_BULB for cell in valid_neighbours(l, c)
     ]
     return Exactly(*neigh_as_light_bulb, count_lightbulbs)
def solve_akari(puzzle, *, height, width):
    X = IntMatrix('n', nb_rows=height, nb_cols=width)

    cell_vars = flatten(X)
    clues = flatten(puzzle)
    assert len(cell_vars) == len(
        clues), "Discrepancy between puzzle and cell-variables"

    complete_c = []
    for cell_var, clue in zip(cell_vars, clues):
        if clue == WALL or clue in (0, 1, 2, 3, 4):
            complete_c.append(cell_var == WALL)
        else:
            complete_c.append(
                Xor(cell_var == LIGHT_BULB, cell_var == NOT_LIGHT_BULB))

    geom = {'width': width, 'height': height}
    at_ = lambda l, c: X[l][c]

    def valid_neighbours(l, c):
        return [
            neigh for neigh in neighbours((l, c))
            if inside_board(neigh, **geom)
        ]

    def gen_count_lightbulb_constraint(l, c, count_lightbulbs):
        neigh_as_light_bulb = [
            at_(*cell) == LIGHT_BULB for cell in valid_neighbours(l, c)
        ]
        return Exactly(*neigh_as_light_bulb, count_lightbulbs)

    count_surrounding_light_bulbs_c = []
    for l, c in itertools.product(range(height), range(width)):
        clue = puzzle[l][c]
        if clue in (0, 1, 2, 3, 4):
            cnstr = gen_count_lightbulb_constraint(l, c, count_lightbulbs=clue)
            count_surrounding_light_bulbs_c.append(cnstr)

    def get_vars_at(indices):
        return [at_(*ind) for ind in indices]

    # think of this as vectorialization of 'value'
    def vars_eq_scalar(variabs, value):
        return [var == value for var in variabs]

    horizontal_cages_indexed = get_horizontal_cages(puzzle)
    vertical_cages_indexed = get_vertical_cages(puzzle)

    horizontal_cages = set(
        tuple(sorted(cage)) for cage in horizontal_cages_indexed.values())
    vertical_cages = set(
        tuple(sorted(cage)) for cage in vertical_cages_indexed.values())

    # two lightbulbs can't illuminate each other. territory? forbid encroachment?

    # there can be at most one bulb in each horizontal cage
    _h_no_encroachment_c = []
    for hcage in horizontal_cages:
        light_bulbs_in_cage = vars_eq_scalar(get_vars_at(hcage), LIGHT_BULB)
        cnstrnt = AtMost(*light_bulbs_in_cage, 1)
        _h_no_encroachment_c.append(cnstrnt)

    _v_no_encroachment_c = []
    for vcage in vertical_cages:
        light_bulbs_in_cage = vars_eq_scalar(get_vars_at(vcage), LIGHT_BULB)
        cnstrnt = AtMost(*light_bulbs_in_cage, 1)
        _v_no_encroachment_c.append(cnstrnt)

    no_encroachment_c = _h_no_encroachment_c + _v_no_encroachment_c

    assert sorted(tuple(vertical_cages_indexed.keys())) == sorted(
        tuple(horizontal_cages_indexed.keys())
    ), "verical cages and horizontal cages must have same keys"

    all_empty_cells_illuminated_c = []
    for coord in vertical_cages_indexed.keys():
        vcage = vertical_cages_indexed[coord]
        hcage = horizontal_cages_indexed[coord]
        horiz_cage_has_lightbulb = Exactly(
            *vars_eq_scalar(get_vars_at(hcage), LIGHT_BULB), 1)
        vertic_cage_has_lightbulb = Exactly(
            *vars_eq_scalar(get_vars_at(vcage), LIGHT_BULB), 1)
        cnstrnt = Or(horiz_cage_has_lightbulb, vertic_cage_has_lightbulb)
        all_empty_cells_illuminated_c.append(cnstrnt)

    s = Solver()
    s.add(complete_c + count_surrounding_light_bulbs_c + no_encroachment_c +
          all_empty_cells_illuminated_c)

    s.check()
    m = s.model()
    return [[m[cell] for cell in row] for row in X]
def solve_tracks(*, tracks, start_index, end_index, horizontal_clues,
                 vertical_clues, height, width):
    X = IntMatrix('t', nb_rows=height, nb_cols=width)
    assert sum(horizontal_clues) == sum(vertical_clues)
    nb_occupied_cells = sum(horizontal_clues)
    MAX = nb_occupied_cells

    cells = list(itertools.chain(*X))
    range_c = [And(n >= 0, n <= nb_occupied_cells) for n in cells]

    at_ = lambda l, c: X[l][c]
    extremities_c = [at_(*start_index) == 1, at_(*end_index) == MAX]
    for index_, pattern in tracks:
        l, c = index_
        bottom, left, top, right = map(int, list(pattern))
        bottom, left, top, right = bottom == 1, left == 1, top == 1, right == 1
        if index_ == start_index:
            # the starting track is the leftmost
            # so it has no left
            if bottom:
                nxt = l + 1, c
            elif top:
                nxt = l - 1, c
            elif right:
                nxt = l, c + 1
            curr_var = at_(*index_)
            nxt_var = at_(*nxt)
            extremities_c.append(nxt_var == curr_var + 1)
        elif index_ == end_index:
            # the ending track is the bottommost
            # so it has no bottom
            if top:
                prv = l - 1, c
            if right:
                prv = l, c + 1
            if left:
                prv = l, c - 1
            curr_var = at_(*index_)
            prv_var = at_(*prv)
            extremities_c.append(curr_var == prv_var + 1)

    X_trans = transpose(X)

    # Exactly count_ occupied (> 0) cells in each row
    row_sums_c = [
        Exactly(*[cell > 0 for cell in row], count_)
        for row, count_ in zip(X, vertical_clues)
    ]

    row_sums_c = [
        Exactly(*[cell > 0 for cell in row], count_)
        for row, count_ in zip(X, vertical_clues)
    ]

    col_sums_c = [
        Exactly(*[cell > 0 for cell in row], count_)
        for row, count_ in zip(X_trans, horizontal_clues)
    ]

    # we give unique_id to unoccupied cell. for using Distinct on occupied cells
    distinct_c = Distinct(
        [If(cell > 0, cell, -i) for i, cell in enumerate(cells, 1)])
    # Every occupied cell is at a given 'distance' to the start.
    # No other cell has the same distance
    distinct_c = [distinct_c]

    def get_adj_track_indices(index_, pattern):
        l, c = index_
        bottom, left, top, right = map(int, list(pattern))
        bottom, left, top, right = bottom == 1, left == 1, top == 1, right == 1
        res = []
        if left:
            # the starting track is the leftmost
            if index_ != start_index:
                res.append((l, c - 1))
        if right:
            res.append((l, c + 1))
        if top:
            res.append((l - 1, c))
        if bottom:
            # the ending track is the bottommost
            if index_ != end_index:
                res.append((l + 1, c))
        res.insert(1, index_)
        return res

    at_ = lambda l, c: X[l][c]

    def get_vars_at(indices):
        return [at_(*ind) for ind in indices]

    def coerce_sequential(vars_):
        curr, *succs = vars_
        ascending_c = And([nxt == curr + i for i, nxt in enumerate(succs, 1)])
        descending_c = And([nxt == curr - i for i, nxt in enumerate(succs, 1)])
        return Or(ascending_c, descending_c)

    # constraint forcing parts of the tracks to be in ascending or descending order
    sequential_c = []
    for index_, pattern in tracks:
        inds = get_adj_track_indices(index_, pattern)
        successive_track_vars = get_vars_at(inds)
        cnstrnt = coerce_sequential(successive_track_vars)
        sequential_c.append(cnstrnt)
    # The start and end track produces an absurd condition. but it would be weeded out by other constraints.

    def gen_consecutive_nums_constraint(l, c):
        geom = {'height': height, 'width': width}
        val_neighs = [
            neigh for neigh in neighbours((l, c))
            if inside_board(neigh, **geom)
        ]
        neighs = get_vars_at(val_neighs)

        var = at_(l, c)  #content of current cell
        one_adj_cell_is_consec_c = Exactly(*[adj == var + 1 for adj in neighs],
                                           1)
        current_cell_is_max_c = var == MAX
        unoccupied_c = var == 0
        return Or(unoccupied_c, current_cell_is_max_c,
                  one_adj_cell_is_consec_c)

    # constraint forcing one of the adjacent cells to be the 'successor'
    successor_c = [
        gen_consecutive_nums_constraint(l, c)
        for l, c in itertools.product(range(height), range(width))
    ]

    s = Solver()
    s.add(range_c + extremities_c + row_sums_c + col_sums_c + distinct_c +
          sequential_c + successor_c)

    s.check()
    m = s.model()

    res = [[m[s] for s in row] for row in X]
    return res
Example #10
0
def coerce_tile(edge_1, edge_2):
    return Exactly(And(edge_1 == POSITIVE, edge_2 == NEGATIVE),
                   And(edge_1 == NEGATIVE, edge_2 == POSITIVE),
                   And(edge_1 == NEUTRAL, edge_2 == NEUTRAL), 1)
Example #11
0
def coerce_charge(edges, polarity, count_):
    edges_with_given_polarity = [edge == polarity for edge in edges]
    return Exactly(*edges_with_given_polarity, count_)
def solve_puzzle_dominosa(puzzle, *, height, width, order):
    board = IntMatrix('d', nb_rows=height, nb_cols=width)

    complete_c = [ Exactly( cell == VERTIC_START, cell == VERTIC_END,
            cell == HORIZ_START, cell == HORIZ_END, 1)
            for cell in flatten(board) ]

    _no_aberrant_horiz_c = []
    for row in board:
        for domino in pairwise(row):
            # not-head and tail
            abberrant_1 = And(domino[0] != HORIZ_START, domino[1] == HORIZ_END)
            # head and not-tail
            _no_aberrant_horiz_c.append(Not(abberrant_1))
            abberrant_2 = And(domino[0] == HORIZ_START, domino[1] != HORIZ_END)
            _no_aberrant_horiz_c.append(Not(abberrant_2))

    _no_aberrant_vertic_c = []
    for row in transpose(board):
        for domino in pairwise(row):
            # not-head and tail
            abberrant_1 = And(domino[0] != VERTIC_START, domino[1] == VERTIC_END)
            _no_aberrant_vertic_c.append(Not(abberrant_1))
            # head and not-tail
            abberrant_2 = And(domino[0] == VERTIC_START, domino[1] != VERTIC_END)
            _no_aberrant_vertic_c.append(Not(abberrant_2))

    _no_aberrant_horiz_border_cell_c = [And(row[0] != HORIZ_END, row[-1] != HORIZ_START)
            for row in board]

    _no_aberrant_vertic_border_cell_c = [And(row[0] != VERTIC_END, row[-1] != VERTIC_START)
            for row in transpose(board)]

    no_aberrant_c = ( _no_aberrant_horiz_c + _no_aberrant_horiz_border_cell_c +
                    _no_aberrant_vertic_c + _no_aberrant_vertic_border_cell_c )

    # By taking both the start_variable and end_variable we may not
    # need no_aberrant_c ... but it's better separated this way.
    unique_c = []
    locs_h = defaultdict(list) #locations
    for var_row, row in zip(board, puzzle):
        for var, dom in zip(var_row, pairwise(row)):
            locs_h[normalize_domino(dom)].append(var)
    locs_v = defaultdict(list)
    for var_row, row in zip(transpose(board), transpose(puzzle)):
        for var, dom in zip(var_row, pairwise(row)):
            locs_v[normalize_domino(dom)].append(var)
    # normalized domino
    for n_domino in itertools.combinations_with_replacement(range(order + 1), 2):
        if n_domino in locs_h and n_domino in locs_v:
            only_one_such_domino = Exactly(*[edge == VERTIC_START for edge in locs_v[n_domino]],
                *[edge == HORIZ_START for edge in locs_h[n_domino]],
                1)
            unique_c.append(only_one_such_domino)
        elif n_domino not in locs_h:
            only_one_such_domino = Exactly(*[edge == VERTIC_START for edge in locs_v[n_domino]], 1)
            unique_c.append(only_one_such_domino)
        elif n_domino not in locs_v:
            only_one_such_domino = Exactly(*[edge == HORIZ_START for edge in locs_h[n_domino]], 1)
            unique_c.append(only_one_such_domino)

    s = Solver()
    s.add(complete_c + no_aberrant_c + unique_c)
    s.check()
    m = s.model()
    return [ [m[cell] for cell in row] for row in board]
Example #13
0
def constrain_towers(tower_vars, tower_height, knowl):
    possiblts = knowl[tower_height]
    all_possiblts = [coerce_eq(tower_vars, possib) for possib in possiblts]
    # Exactly one possibility among all would satisfy.
    return Exactly(*all_possiblts, 1)
Example #14
0
 def coerce_nb_tents(cells, count_):
     tents = [cell < 0 for cell in cells]
     return Exactly(*tents, count_)
Example #15
0
def gen_constraints_vars_runs(vars_, runs):
    possibs = [ coerce_eq(vars_, pat)
            for pat in get_patterns_given_runs(runs, len(vars_)) ]
    return Exactly(*possibs, 1)