예제 #1
0
class ConstraintSatisfactionProblem:
    def __init__(self, assignment_length, domain_length, constraints):
        self.fails = 0

        self.assignment = []  # array of ints
        self.assignment_length = assignment_length  # int corresponding to number of variables
        # set to none and of proper size
        for i in range(assignment_length):
            self.assignment.append(None)

        self.domain_length = domain_length  # int corresponding to number of entries in domain

        self.constraints = Constraint(constraints)

    # runs setup, then starts recursive backtrack search
    def backtrack_search(self, mrv, lcv, infer):
        # reset some necessary instance variables
        self.fails = 0
        for i in range(self.assignment_length):
            self.assignment[i] = None

        # calls the recursive backtrack search
        self.backtrack_search_helper(mrv, lcv, infer, {})

    # recursive backtrack search
    def backtrack_search_helper(self, mrv, lcv, infer, inconsistencies):

        print("Decided:", self.assignment)

        # base case: assignment is complete and valid
        if self.is_complete() and self.constraints.is_satisfied(
                self.assignment):
            return self.assignment

        # minimum remaining values heuristic
        if mrv:
            var = self.mrv_heuristic()
        else:
            var = self.no_variable_heuristic()

        # least constraining value heuristic
        if lcv:
            domain_list = self.lcv_heuristic(var)
        else:
            domain_list = []
            for i in range(self.domain_length):
                domain_list.append(i)

        if infer:  # find infer values
            if var in inconsistencies.keys():
                for inconsistency in inconsistencies[var]:
                    if inconsistency in domain_list:
                        domain_list.remove(
                            inconsistency)  # don't consider inconsistencies

        #print("domain list: ", domain_list)
        for val in domain_list:

            self.assignment[var] = val
            print("trying val; ", val, ";", self.assignment, ";",
                  self.constraints.is_satisfied(self.assignment))

            if self.constraints.is_satisfied(self.assignment):
                if infer:
                    inconsistencies = self.mac_infer(
                        var)  # find inconsistencies from inference
                    #print(inconsistencies)
                    if inconsistencies is not None:
                        result = self.backtrack_search_helper(
                            mrv, lcv, infer, inconsistencies)
                    else:
                        return None

                else:
                    result = self.backtrack_search_helper(
                        mrv, lcv, infer, inconsistencies)

                if result is not None:  # potential value found, use it for previous recursive iteration
                    return result

            else:  # increment fails
                self.fails += 1

            # backtracking
            self.assignment[var] = None

        # no solution, return None
        return None

    # checks to see if assignment is complete
    def is_complete(self):
        for i in range(self.assignment_length):
            if self.assignment[i] is None:
                return False
        return True

    # returns next variable
    def no_variable_heuristic(self):
        for i in range(self.assignment_length):
            if self.assignment[i] is None:
                return i
                #return range(i, self.assignment_length)
        return 0

    # returns variable based on minimum remaining values
    def mrv_heuristic(self):

        min_remaining = (None, float('-inf'))
        # iterate through unassigned variables
        for i in range(self.assignment_length):
            if self.assignment[i] is None:
                temp = self.constraints.possible_count(self.assignment, i)
                # if more constraining than previous most constraining, it is the new most constraint
                if temp > min_remaining[1]:
                    min_remaining = (i, temp)

        return min_remaining[0]

    # returns values based on least constrained value
    def lcv_heuristic(self, var):

        tuple_list = []
        domain_list = []

        # iterate through each possible value
        for i in range(self.domain_length):
            lc_val = self.constraints.constrain_count(self.assignment, var, i)

            #print("*", lc_val)

            # find correct index to insert
            j = 0
            index = 0
            while j < len(tuple_list):
                if tuple_list[j][1] < lc_val[1]:
                    index += 1
                j += 1
            tuple_list.insert(index, lc_val)

        for i in range(len(tuple_list)):
            domain_list.append(tuple_list[i][0])

        #print("domain list:",domain_list, "\n")
        return domain_list

    # mac-3 inference
    def mac_infer(self, var):
        # dictionary mapping unassigned var ints to set of values that they cannot be:
        inconsistencies = {}
        queue = []

        # add all arcs with with one unassinged variables and given variable in them
        for initial in self.constraints.unassigned_neighbors(
                self.assignment, var):
            queue.append(initial)

        while queue:  # iterate through arcs
            arc = queue.pop()
            temp = self.mac_revise(arc, inconsistencies)

            # occurs when no possible value for some variable, means this tree is bad
            if temp is None:
                return None

            # add inconsistencies if found
            inconsistencies[arc[0]] = temp

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

    # determines if arc is an inconsistency
    def mac_revise(self, arc, inconsistencies):
        # set of all values var1 and var2 cannot be
        revised = set()
        if arc[0] in inconsistencies.keys():
            revised = inconsistencies[arc[0]]

        constraints = self.constraints.get_constraints(arc[0], arc[1])
        #print("constraints:",constraints)
        if constraints is None:
            return None

        # iterate through assignment, looking for inconsistencies
        for x in range(self.domain_length):
            cannot = 0
            for y in range(self.domain_length):
                if (x, y) not in constraints:
                    cannot += 1
            if cannot == self.domain_length:
                revised.add(x)

        # no possible values, bad decision in past
        if len(revised) == self.domain_length:
            return None

        return revised