Example #1
0
    def __init__(self, length, height, pieces_list, square):
        self.length = length            # length of grid
        self.height = height            # height of grid
        self.pieces_list = pieces_list  # list of pieces

        self.constraints = self.set_constraints(square)     # constraints
        self.CSP = ConstraintSatisfactionProblem(len(pieces_list), self.length * self.height, self.constraints)  # CSP
    def __init__(self,
                 name,
                 piecemap,
                 n,
                 m,
                 MRV=True,
                 DH=False,
                 LCV=True,
                 AC3=True,
                 print=False):
        self.name = name

        self.n = n
        self.m = m

        self.piecemap = piecemap  # char -> (w,h)
        self.charmap = self.__charmap()  # int -> char
        self.inverse_charmap = {
            value: key
            for key, value in self.charmap.items()
        }  # char -> int

        # self.colormap = self.__colormap() # int -> str
        self.neighbors = self.__genCompleteGraph()

        # construct domain
        domain = self.__domainmap(n, m)
        # construct constraints
        constraints = self.__constraintmap(domain)

        self.CSP = ConstraintSatisfactionProblem(self.neighbors, domain,
                                                 constraints, MRV, DH, LCV,
                                                 AC3, print)
        self.solution = self.__find_solution()
Example #3
0
    def __init__(self, given_numbers):

        self.given_numbers = given_numbers  # format: (value, x, y); 0,0 is the bottom left corner
        self.domain = [1, 2, 3, 4, 5, 6, 7, 8, 9]

        self.given_dict = {}  # key is position, value is value
        # givens
        for given in self.given_numbers:
            self.given_dict[self.coord_to_int(given[1], given[2])] = given[0]

        self.constraints = self.set_constraints()
        self.CSP = ConstraintSatisfactionProblem(
            81, 10, self.constraints)  # hardcoded because sudoku
    def __init__(self, name, neighborgraph, MRV = True, DH = False, LCV = True, AC3 = True, print = False):
        self.name = name

        self.strmap = self.__strmap(neighborgraph) # int -> str
        self.inverse_strmap = {value: key for key, value in self.strmap.items()} # str -> int
        self.colormap = self.__colormap() # int -> str
        self.neighbors = self.__neighborgraphToInt(neighborgraph)

        # construct domain
        domain = self.__domainmap()
        # construct constraints
        constraints = self.__constraintmap()

        self.CSP = ConstraintSatisfactionProblem(self.neighbors, domain, constraints, MRV, DH, LCV, AC3, print)
        self.solution = self.__find_solution()
Example #5
0
    def __init__(self, map_filename):
        self.variables = []
        self.domain = None
        self.int_domains = []
        self.constraint_pairs = []
        self.constraints = []
        self.connections = []

        f = open(map_filename)
        line_num = 0
        for line in f:
            line = line.strip()
            components = line.split(", ")

            if line_num == 0:
                self.variables = components

            if line_num == 1:
                self.generate_domains(tuple(components))

            if line_num == 2:
                self.init_connections()
                self.generate_pairs_connections(components)

            line_num += 1

        f.close()

        self.legal_constraint_values = self.legal_values(self.int_domains[0])
        self.generate_constraints()

        self.int_csp = ConstraintSatisfactionProblem(len(self.variables),
                                                     self.connections,
                                                     self.int_domains,
                                                     self.constraints)
Example #6
0
    def __init__(self, board_filename):
        self.board_n = 0
        self.board_m = 0
        self.variables = []
        self.domains = []
        self.constraint_pairs = []
        self.constraints = []
        self.connections = []

        f = open(board_filename)
        line_num = 0

        prev_char = ""
        prev_line = ""
        rows = 0

        for line in f:
            line = line.strip()
            if line_num == 0:
                line = line.split("x")
                self.board_n = int(line[0])
                self.board_m = int(line[1])
            else:
                cur_char = line[0]
                if (prev_char == ""):
                    prev_char = cur_char
                if (cur_char != prev_char):
                    piece = BoardPiece(prev_char, len(prev_line), rows)
                    self.variables.append(piece)
                    rows = 0

                prev_line = line
                prev_char = cur_char
                rows += 1
            line_num += 1

        f.close()

        # Take care of the last piece in the text file
        last_piece = BoardPiece(prev_char, len(prev_line), rows)
        self.variables.append(last_piece)

        self.generate_domains()
        self.generate_connections()
        self.generate_constraint_pairs()
        self.generate_all_constraints()

        self.int_csp = ConstraintSatisfactionProblem(len(self.variables),
                                                     self.connections,
                                                     self.domains,
                                                     self.constraints)
class MapColoringCSP:
    def __init__(self, name, neighborgraph, MRV = True, DH = False, LCV = True, AC3 = True, print = False):
        self.name = name

        self.strmap = self.__strmap(neighborgraph) # int -> str
        self.inverse_strmap = {value: key for key, value in self.strmap.items()} # str -> int
        self.colormap = self.__colormap() # int -> str
        self.neighbors = self.__neighborgraphToInt(neighborgraph)

        # construct domain
        domain = self.__domainmap()
        # construct constraints
        constraints = self.__constraintmap()

        self.CSP = ConstraintSatisfactionProblem(self.neighbors, domain, constraints, MRV, DH, LCV, AC3, print)
        self.solution = self.__find_solution()

    def __find_solution(self):
        return self.CSP.get_assignment()

    def __strmap(self, neighborgraph):
        strset = list(neighborgraph.keys()) # list for replicability
        # random.shuffle(strset)
        # print(strset)

        strmap = {key: strset.pop() for key in range(0, len(strset), 1)}
        # print(strmap)


        return strmap

    def __colormap(self, numcolors = 3):
        colorset = [ "Blue", "Green", "Red"] # list for replicability

        colormap = {key: colorset.pop() for key in range(0, len(colorset), 1)}

        # print(colormap)
        return colormap

    def __neighborgraphToInt(self, neighborgraph):
        neighbors = {}

        for variable in neighborgraph:
            vneighbors = neighborgraph[variable]

            # if there is an error, it will be here. ensure the neighbor graph is valid.
            intneighbors = set()
            for neighbor in vneighbors:
                if neighbor in self.inverse_strmap:
                    intneighbors.add(self.inverse_strmap[neighbor])
                else:
                    print("Error with neighbors of " + variable)
                    intneighbors.add(self.inverse_strmap[neighbor])

            # { self.inverse_strmap[neighbor] for neighbor in vneighbors }


            neighbors[self.inverse_strmap[variable]] = intneighbors

        # print(neighbors)
        return neighbors


    def __domainmap(self):
        domain = {}

        for variable in self.neighbors:
            domain[variable] = set(self.colormap.keys())


        # print(domain)
        return domain

    def __constraintmap(self):
        colorsneq = set()

        # each relation has the same constraint
        for color1 in self.colormap:
            for color2 in self.colormap:
                if color1 != color2:
                    colorsneq.add((color1, color2))

        constraints = {}
        for i in range(0, len(self.neighbors), 1):
            for j in self.neighbors[i]:
                # neighbors is undirected, so don't add duplicate constraints
                if i < j:
                    constraints[(i,j)] = colorsneq.copy()

        # print(constraints)
        return constraints

    def __str__(self):
        string = "----\n"
        string += "Map Coloring CSP Problem: {:s}\n"
        string += "Heuristics/Inference:  MRV: {:s}. DH: {:s} LCV: {:s}. AC3: {:s}\n"

        if self.solution:
            string += "Number of recursion calls: {:d}\n"
            string += "Solution: {:s}\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG), str(self.CSP.DH_FLAG), str(self.CSP.LCV_FLAG), str(self.CSP.AC3_FLAG), self.CSP.nodes_visited, str(self.__solutionToStr()))
        else:
            string += "No solution found after {:d} recursion calls\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG), str(self.CSP.DH_FLAG), str(self.CSP.LCV_FLAG), str(self.CSP.AC3_FLAG), self.CSP.nodes_visited)

        return string

        # returns a dictionary
    def __solutionToStr(self):
        coloredInMap = {}

        # i reverse the map to match the input format
        for i in range(len(self.solution)-1, -1, -1):
            coloredInMap[self.strmap[i]] = self.colormap[self.solution[i]]

        return coloredInMap
Example #8
0
class SudokuCSP:
    def __init__(self, given_numbers):

        self.given_numbers = given_numbers  # format: (value, x, y); 0,0 is the bottom left corner
        self.domain = [1, 2, 3, 4, 5, 6, 7, 8, 9]

        self.given_dict = {}  # key is position, value is value
        # givens
        for given in self.given_numbers:
            self.given_dict[self.coord_to_int(given[1], given[2])] = given[0]

        self.constraints = self.set_constraints()
        self.CSP = ConstraintSatisfactionProblem(
            81, 10, self.constraints)  # hardcoded because sudoku

    def set_constraints(self):
        # first find the domain of every single value
        single_var_constraint = {}
        for x in range(10):
            for y in range(10):
                if self.coord_to_int(x, y) not in self.given_dict.keys():
                    local_can = self.local_can_values(x, y)
                    vertical_can = self.vertical_can_values(y)
                    horizontal_can = self.horizontal_can_values(x)

                    valid_vals = set()
                    for i in range(10):  # domain values that given x,y can be
                        if i in local_can and vertical_can and horizontal_can:
                            valid_vals.add(i)
                    single_var_constraint[self.coord_to_int(x, y)] = valid_vals

        # now create constraints
        constraints = {}
        for x_1 in range(10):
            for y_1 in range(10):
                for x_2 in range(10):
                    for y_2 in range(10):
                        if x_1 != x_2 and y_1 != y_2:  # check to make sure not the same square
                            common_location_list1 = set()
                            common_location_list2 = set()
                            if self.coord_to_int(
                                    x_1, y_1) in single_var_constraint.keys():
                                if self.coord_to_int(
                                        x_2,
                                        y_2) in single_var_constraint.keys():
                                    for val1 in single_var_constraint[
                                            self.coord_to_int(x_1, y_1)]:
                                        for val2 in single_var_constraint[
                                                self.coord_to_int(x_2, y_2)]:
                                            if self.no_influence(
                                                    x_1, y_1, x_2, y_2):
                                                common_location_list1.add(
                                                    (val1, val2))
                                                common_location_list2.add(
                                                    (val2, val1))
                                            else:  # add all values except same values
                                                if val1 != val2:
                                                    common_location_list1.add(
                                                        (val1, val2))
                                                    common_location_list2.add(
                                                        (val2, val1))
                            if len(common_location_list1) > 0:
                                constraints[
                                    self.coord_to_int(x_1, y_1),
                                    self.coord_to_int(
                                        x_2, y_2)] = common_location_list1
                            if len(common_location_list2) > 0:
                                constraints[
                                    self.coord_to_int(x_2, y_2),
                                    self.coord_to_int(
                                        x_1, y_1)] = common_location_list2

        #print("constraints:",constraints)
        return constraints

    def no_influence(self, x_1, y_1, x_2, y_2):
        # see if on same row
        test1 = False
        test2 = False
        for x_loc in range(10):
            if x_loc == x_1:
                test1 = True
            if x_loc == x_2:
                test2 = True
        if test1 and test2:
            return False

        # see if on same column
        test1 = False
        test2 = False
        for y_loc in range(10):
            if y_loc == y_1:
                test1 = True
            if y_loc == y_2:
                test2 = True
        if test1 and test2:
            return False

        # test to see if in same 9-group:
        if 0 <= x_1 <= 2 and 0 <= y_1 <= 2 and 0 <= x_2 <= 2 and 0 <= y_2 <= 2:  # bottom left
            return False
        if 3 <= x_1 <= 5 and 0 <= y_1 <= 2 and 3 <= x_2 <= 5 and 0 <= y_2 <= 2:  # bottom middle
            return False
        if 6 <= x_1 <= 8 and 0 <= y_1 <= 2 and 6 <= x_2 <= 8 and 0 <= y_2 <= 2:  # bottom right
            return False
        if 0 <= x_1 <= 2 and 3 <= y_1 <= 5 and 0 <= x_2 <= 2 and 3 <= y_2 <= 5:  # middle left
            return False
        if 3 <= x_1 <= 5 and 3 <= y_1 <= 5 and 3 <= x_2 <= 5 and 3 <= y_2 <= 5:  # middle middle
            return False
        if 6 <= x_1 <= 8 and 3 <= y_1 <= 5 and 6 <= x_2 <= 8 and 3 <= y_2 <= 5:  # middle right
            return False
        if 0 <= x_1 <= 2 and 6 <= y_1 <= 8 and 0 <= x_2 <= 2 and 6 <= y_2 <= 8:  # top left
            return False
        if 3 <= x_1 <= 5 and 6 <= y_1 <= 8 and 3 <= x_2 <= 5 and 6 <= y_2 <= 8:  # top middle
            return False
        if 6 <= x_1 <= 8 and 6 <= y_1 <= 8 and 6 <= x_2 <= 8 and 6 <= y_2 <= 8:  # top right
            return False

        return True

    def vertical_can_values(self, y):
        good = set()
        bad = set()
        for x_loc in range(10):
            loc = self.coord_to_int(x_loc, y)
            if loc in self.given_dict.keys():
                bad.add(self.given_dict[loc])

        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def horizontal_can_values(self, x):
        good = set()
        bad = set()
        for y_loc in range(10):
            loc = self.coord_to_int(x, y_loc)
            if loc in self.given_dict.keys():
                bad.add(self.given_dict[loc])

        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    # returns set of values that it cannot be, given x and y, based on given values:
    def local_can_values(
        self,
        x,
        y,
    ):

        # find location within square of 9
        if x % 3 == 0:
            if y % 3 == 0:  # bottom left corner
                can_values = self.bottom_left(x, y)
            elif y % 3 == 1:  # bottom middle
                can_values = self.bottom_middle(x, y)
            else:  # bottom right
                can_values = self.bottom_right(x, y)
        elif x % 3 == 1:
            if y % 3 == 0:  # middle left corner
                can_values = self.middle_left(x, y)
            elif y % 3 == 1:  # middle middle
                can_values = self.middle_middle(x, y)
            else:  # middle right
                can_values = self.middle_right(x, y)
        else:
            if y % 3 == 0:  # top left corner
                can_values = self.top_left(x, y)
            elif y % 3 == 1:  # top middle
                can_values = self.top_middle(x, y)
            else:  # top right
                can_values = self.top_middle(x, y)

        return can_values

    def bottom_left(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x, x + 3):
            for y_loc in range(y, y + 3):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def bottom_middle(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 1, x + 2):
            for y_loc in range(y, y + 3):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def bottom_right(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 2, x + 1):
            for y_loc in range(y, y + 3):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def middle_left(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x, x + 3):
            for y_loc in range(y - 1, y + 2):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def middle_middle(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 1, x + 2):
            for y_loc in range(y - 1, y + 2):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def middle_right(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 2, x + 1):
            for y_loc in range(y - 1, y + 2):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def top_left(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x, x + 3):
            for y_loc in range(y - 2, y + 1):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def top_middle(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 1, x + 2):
            for y_loc in range(y - 2, y + 1):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    def top_right(self, x, y):
        good = set()
        bad = set()
        for x_loc in range(x - 2, x + 1):
            for y_loc in range(y - 2, y + 1):
                if x_loc != x and y_loc != y:
                    loc = self.coord_to_int(x_loc, y_loc)
                    if loc in self.given_dict.keys():
                        bad.add(self.given_dict[loc])
        for i in range(len(self.domain)):
            if i not in bad:
                good.add(self.domain[i])
        return good

    # these can be hardcoded because it is sudoku
    def coord_to_int(self, x, y):
        return y * 10 + x

    def int_to_coord(self, var):
        return var % 10, int(var / 10)

    # calls backtrack search object from CSP, and returns output plus some syntax
    def backtrack_search(self, mrv, lcv, inference):
        print("this happened")
        self.CSP.backtrack_search(mrv, lcv, inference)
        print(self.CSP.assignment)
Example #9
0
class SudokuCSP:
    # len(board) must be a perfect square
    def __init__(self,
                 name,
                 board,
                 MRV=True,
                 DH=False,
                 LCV=True,
                 AC3=True,
                 print=False):
        self.name = name

        # self.strmap = self.__strmap(neighborgraph) # int -> str
        # self.inverse_strmap = {value: key for key, value in self.strmap.items()} # str -> int
        # self.colormap = self.__colormap() # int -> str
        self.neighbors = self.__genNeighbors(board)

        # construct domain
        domain = self.__domainmap(board)
        # construct constraints
        constraints = self.__constraintmap(board)

        self.CSP = ConstraintSatisfactionProblem(self.neighbors, domain,
                                                 constraints, MRV, DH, LCV,
                                                 AC3, print)
        self.solution = self.__find_solution()

    def __find_solution(self):
        print("starting testing")
        return self.CSP.get_assignment()

        # each spot's neighbors are its column, row, and box
    def __genNeighbors(self, board):
        neighbormap = {}

        n = len(board)
        s = int(math.sqrt(len(board)))

        for i in range(0, n, 1):
            for j in range(0, n, 1):
                neighbors = set()

                boxi = i // s * s
                boxj = j // s * s

                # box
                for x in range(boxi, boxi + s, 1):
                    for y in range(boxj, boxj + s, 1):
                        if x != i and y != j:
                            neighbors.add(self.__coordToIndex(x, y, n))

                # column
                for y in range(0, n, 1):
                    if y != j:
                        neighbors.add(self.__coordToIndex(i, y, n))

                # row
                for x in range(0, n, 1):
                    if x != i:
                        neighbors.add(self.__coordToIndex(x, j, n))

                neighbormap[self.__coordToIndex(i, j, n)] = neighbors

        return neighbormap

    def __coordToIndex(self, x, y, n):
        return x + y * n

    def __domainmap(self, board):
        allnums = {1, 2, 3, 4, 5, 6, 7, 8, 9}
        domain = {}

        n = len(board)

        for x in range(0, n, 1):
            for y in range(0, n, 1):
                # domain is all numbers
                if board[y][x] == 0:
                    domain[self.__coordToIndex(x, y, n)] = allnums.copy()
                # domain is one number
                else:
                    domain[self.__coordToIndex(x, y, n)] = {board[y][x]}

        return domain

    def __constraintmap(self, board):
        constraints = {}

        # this will be the set of all possible combinations of numbers
        numsneq = set()
        for i in range(1, len(board) + 1, 1):
            for j in range(1, len(board) + 1, 1):
                if i != j:
                    numsneq.add((i, j))

        # neighbors will have this constraint
        for variable in self.neighbors:
            for neighbor in self.neighbors[variable]:
                if variable < neighbor:
                    constraints[(variable, neighbor)] = numsneq.copy()

        return constraints

    def __str__(self):
        string = "----\n"
        string += "Map Coloring CSP Problem: {:s}\n"
        string += "Heuristics/Inference:  MRV: {:s}. DH: {:s} LCV: {:s}. AC3: {:s}\n"

        if self.solution:
            string += "Number of recursion calls: {:d}\n"
            string += "Solution: {:s}\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG),
                                   str(self.CSP.DH_FLAG),
                                   str(self.CSP.LCV_FLAG),
                                   str(self.CSP.AC3_FLAG),
                                   str(self.__solutionToStr()))
        else:
            string += "No solution found after {:d} recursion calls\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG),
                                   str(self.CSP.DH_FLAG),
                                   str(self.CSP.LCV_FLAG),
                                   str(self.CSP.AC3_FLAG),
                                   self.CSP.nodes_visited)

        return string

    def __solutionToStr(self):
        n = int(math.sqrt(len(self.solution)))
        s = int(math.sqrt(n))  # size of each sub-box

        string = "\n"

        for i in range(0, len(self.solution), 1):
            value = self.solution[i]
            if value < 10:
                value = str(value) + "  "
            else:
                value = str(value) + " "

            if (i + 1) % (n * s) == 0:
                string += value + "\n" + "-" * (3 * n + 2) + "\n"
            elif (i + 1) % n == 0:
                string += value + "\n"
            elif (i + 1) % s == 0:
                string += str(value) + "|"
            else:
                string += str(value)

        return string
Example #10
0
class CircuitBoardCSP:
    def __init__(self, length, height, pieces_list, square):
        self.length = length            # length of grid
        self.height = height            # height of grid
        self.pieces_list = pieces_list  # list of pieces

        self.constraints = self.set_constraints(square)     # constraints
        self.CSP = ConstraintSatisfactionProblem(len(pieces_list), self.length * self.height, self.constraints)  # CSP

    # set constraints for CSP
    def set_constraints(self, square):  # square format: (letter, length, height)
        constraints = {}

        pieces_location_list = []   # list of sets of all legal left-hand corners of each piece, for each piece
        for piece in self.pieces_list:
            # get set of all legal left-hand corners of each piece
            piece_location_set = set()
            for y in range(self.height):
                for x in range(self.length):
                    if x + piece[1] - 1 < self.length and y + piece[2] - 1 < self.height:
                        piece_location_set.add((piece, x, y))   # potential location of piece, and that piece
                        #print(piece_location_set)
            pieces_location_list.append(piece_location_set)

        for i in range(len(pieces_location_list)):
            for j in range(len(pieces_location_list)):
                if i != j:  # make sure we aren't comparing the same piece
                    common_location_list1 = set()    # set of all legal left-hand corners of pieces i and j
                    common_location_list2 = set()

                    for i_loc in pieces_location_list[i]:
                        for j_loc in pieces_location_list[j]:
                            #print("i_loc", i_loc, "j_loc:", j_loc)
                            #print(collision(i_loc, j_loc))
                            if not collision(i_loc, j_loc):
                                #print("adding:", (self.coord_to_int(i_loc[1], i_loc[2]),
                                # self.coord_to_int(j_loc[1], j_loc[2])  ))
                                common_location_list1.add( (self.coord_to_int(i_loc[1], i_loc[2]),
                                                           self.coord_to_int(j_loc[1], j_loc[2])  ))
                                common_location_list2.add( (self.coord_to_int(j_loc[1], j_loc[2]),
                                                           self.coord_to_int(i_loc[1], i_loc[2])  ))
                    constraints[(i, j)] = common_location_list1
                    constraints[(j, i)] = common_location_list2

        return constraints

    # given x and y values, return corresponding single int for some grid
    def coord_to_int(self, x, y):
        return y * self.length + x

    # given single int for some grid, return corresponding x and y values
    def int_to_coord(self, var):
        return var % self.length, int(var / self.length)

    # calls backtrack search object from CSP, and returns output plus some syntax
    def backtrack_search(self, mrv, lcv, inference):
        self.CSP.backtrack_search(mrv, lcv, inference)
        return self.solution_to_str(self.CSP.assignment) + "\n" + str(self.CSP.fails) + " fails" + "\n"

    # returns solution with nice syntax, rather than integers
    def solution_to_str(self, solution):
        sol_dict = {}
        sol_str = ""

        # iterate through pieces:
        for i in range(len(self.pieces_list)):
            # iterate through x and y values in each piece
            for x in range(self.pieces_list[i][1]):
                for y in range(self.pieces_list[i][2]):
                    # set that key to corresponding character symbol representing piece
                    pos = self.int_to_coord(solution[i])
                    ins = self.coord_to_int(x + pos[0], y + pos[1])
                    sol_dict[ins] = self.pieces_list[i][0]

        # setting blank spaces to periods
        for i in range(self.length * self.height):
            if i not in sol_dict.keys():
                sol_dict[i] = "."

        # turning dictionary into string
        for i in range(len(sol_dict.keys())):
            if i != 0 and i % self.length == 0:
                sol_str += "\n"
            sol_str += sol_dict[i]

        return sol_str
class CircuitBoardCSP:
    # piecemap: {char: (width, height))
    def __init__(self,
                 name,
                 piecemap,
                 n,
                 m,
                 MRV=True,
                 DH=False,
                 LCV=True,
                 AC3=True,
                 print=False):
        self.name = name

        self.n = n
        self.m = m

        self.piecemap = piecemap  # char -> (w,h)
        self.charmap = self.__charmap()  # int -> char
        self.inverse_charmap = {
            value: key
            for key, value in self.charmap.items()
        }  # char -> int

        # self.colormap = self.__colormap() # int -> str
        self.neighbors = self.__genCompleteGraph()

        # construct domain
        domain = self.__domainmap(n, m)
        # construct constraints
        constraints = self.__constraintmap(domain)

        self.CSP = ConstraintSatisfactionProblem(self.neighbors, domain,
                                                 constraints, MRV, DH, LCV,
                                                 AC3, print)
        self.solution = self.__find_solution()

    def __find_solution(self):
        return self.CSP.get_assignment()

    def __charmap(self):
        charset = list(self.piecemap.keys())  # list for replicability
        # random.shuffle(strset)
        # print(strset)

        charmap = {key: charset.pop() for key in range(0, len(charset), 1)}

        # print(charmap)
        return charmap

        # everything is a neighbor to everything
    def __genCompleteGraph(self):
        completeGraph = {}

        pieces = self.charmap.keys()

        for piece in self.charmap:
            neighbors = set(pieces)
            neighbors.remove(piece)
            completeGraph[piece] = neighbors

        # print(completeGraph)
        return completeGraph

    def __domainmap(self, n, m):
        domain = {}

        for variable in self.neighbors:
            possibleCoordinates = set()

            length = self.piecemap[self.charmap[variable]][0]
            height = self.piecemap[self.charmap[variable]][1]

            # only possible locations are those which fit in the n x m space
            for x in range(0, n - length + 1, 1):
                for y in range(0, m - height + 1, 1):
                    possibleCoordinates.add((x, y))

            domain[variable] = possibleCoordinates

        # print(domain)
        return domain

    def __constraintmap(self, domain):
        # idea: we set a piece at coordinate (x,y). That means no other piece can be between (x,y) and (x+length,y+height). so, (0,0)
        constraints = {}

        # find out constraints for (i,j) pair in complete graph
        for vari in range(0, len(self.neighbors) - 1, 1):
            for varj in range(vari + 1, len(self.neighbors), 1):
                var_pair = (vari, varj)
                constraints[var_pair] = self.__genCombos(domain, vari, varj)

        # print(constraints)

        return constraints

    def __genCombos(self, domain, vari, varj):
        possibleCombos = set()

        lengthi = self.piecemap[self.charmap[vari]][0]
        heighti = self.piecemap[self.charmap[vari]][1]
        lengthj = self.piecemap[self.charmap[varj]][0]
        heightj = self.piecemap[self.charmap[varj]][1]

        # loop over the domain of vari and domain of varj and if they do not
        # overlap, add them to possible combos
        for coordi in domain[vari]:
            for coordj in domain[varj]:
                # vari on other side of varj
                if coordi[0] + lengthi - 1 < coordj[
                        0] or coordj[0] + lengthj - 1 < coordi[0]:
                    possibleCombos.add((coordi, coordj))
                # vari on top/below varj
                elif coordi[1] + heighti - 1 < coordj[
                        1] or coordj[1] + heightj - 1 < coordi[1]:
                    possibleCombos.add((coordi, coordj))

        return possibleCombos

    def __str__(self):
        string = "----\n"
        string += "Circuit Board CSP Problem: {:s}\n"
        string += "Heuristics/Inference:  MRV: {:s}. DH {:s}. LCV: {:s}. AC3: {:s}\n"

        if self.solution:
            string += "Number of recursion calls: {:d}\n"
            string += "Solution:\n"
            string += "{:s}\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG),
                                   str(self.CSP.DH_FLAG),
                                   str(self.CSP.LCV_FLAG),
                                   str(self.CSP.AC3_FLAG),
                                   self.CSP.nodes_visited,
                                   str(self.__solutionToStr()))
        else:
            string += "No solution found after {:d} recursion calls\n"

            string = string.format(self.name, str(self.CSP.MRV_FLAG),
                                   str(self.CSP.DH_FLAG),
                                   str(self.CSP.LCV_FLAG),
                                   str(self.CSP.AC3_FLAG),
                                   self.CSP.nodes_visited)

        return string

        # returns a dictionary
    def __solutionToStr(self):
        labeled = {}

        # i reverse the map to match the input format
        for i in range(len(self.solution) - 1, -1, -1):
            labeled[self.charmap[i]] = self.solution[i]

        string = str(labeled) + "\n"

        ASCII = [None] * (self.n * self.m)

        for i in range(0, len(self.solution), 1):
            length = self.piecemap[self.charmap[i]][0]
            height = self.piecemap[self.charmap[i]][1]
            bottom_left = self.solution[i]

            for x in range(bottom_left[0], bottom_left[0] + length, 1):
                for y in range(bottom_left[1], bottom_left[1] + height, 1):
                    ASCII[self.__pointToIndex((x, y))] = self.charmap[i]

        for i in range(0, len(ASCII), 1):
            if i % self.n == 0:
                string += "\n"

            if ASCII[i] is None:
                string += "."
            else:
                string += ASCII[i]

        return string

    def __pointToIndex(self, coord):
        return coord[0] + (self.m - coord[1] - 1) * self.n