Week 06: Constraint Satisfaction Problems

These problems are characterized by:

A solution is a n-tuple of values for the variables that satisfies the constraints.

Examples

Australian map colouring:

Sudoku:

Eight queens puzzle:

Basic Algorithms

Generate-and-Test algorithm

Generate the assignment space D=Dv1××DvnD = D_{v_1} \times \dots \times D_{v_n} (cartesian product), then test each assignment with the constraints.

It is exponential in the number of variables.

Backtracking

Systematically explore D by instantiating variables one at a time, evaluating each constraint as all its variables are bound.

Thus, any partial assignment that doesn’t satisfy the constraints can be pruned (e.g. if ABA \neq B, can prune these even if CC, DD etc. have not been instantiated yet).

A node is an assignment of values to some of the variables.

Search:

The start node is the empty assignment, and the goal node is a total assignment that satisfies the constraints.

CSP Algorithms

CSP is NP-hard. However, some instances of CSP can be solved more efficiently by exploiting certain properties.

Constraint Networks

An instance of CSP can be represented as a network:

Arc Consistency

An arc X,r(X,Y)\langle X, r(X, \overline{Y})\rangle is arc consistent if for every value of XX, there a value of Y\overline{Y} such that r(x,y)r(x,\overline{y}) is satisfied. Y\overline{Y} may be a set of multiple variables (variables in the scope of the constraint, except XX).

A network is arc consistent if all arcs are arc consistent.

If an constraint has only one variable in its scope and every value in the domain satisfies the constraint, then the arc is domain consistent.

If there is an arc that is not arc consistent, remove values from XX’s domain to make it arc consistent.

Example:

One arc would be A,A+B=C\langle{A, A+B=C}\rangle:

By repeating this with other nodes, the network can be made arc consistent.

Arc Consistency Algorithm

Arcs can be considered in series. An arc, X,r(X,Y)\langle X, r(X, \overline{Y})\rangle, needs to be revisited if the domain of one of the YY’s is reduced.

def GAC(variables, domains, constraints):
  # GAC: Generalized Arc Consistency algorithm
  return GAC2(variables, domains, constraints, [((X, C) for C in constraints if X in scope(C)) for X in variables].flatten())

def GAC2(variables, domains, constraints, todo):
  while not todo.isEmpty():
    X, constraint = todo.pop() # The variable is in the scope of the constraint (i.e. it is relevant)
    Y = scope(todo)
    Y.pop(X) # Need to remove the chosen from the set of constraints

    new_domain = [x for x in domain(X)
      # such that there exists a set (y_1,..., y_n) such that is_consistent(X=x, Y_1=y_1, ..., Y_n=y_n)
    ]

    if new_domain != domain(X):
      # need to re-check all variables that are involved with any constraints involving X
      todo += [(Z, C) for C in constraints if X in scope(C) and Z in scope(C) and X != Z]
      domain(X) = new_domain

There are three possible outcomes of this algorithm:

Domain Splitting

Split a problem into a number of disjoint cases and solve each case separately: the set of solutions is the union of all solutions to each case.

e.g. if X0,1X \in {0,1}, find all solutions where X=0X=0, then where X=1X=1.

Algorithm

Given a CSP instance:

def CSPSolver(variables, domains, constraints, todo):
  if [domain for domain in domains if len(domain) == 0] is not empty:
    return False
  else if [domain[0] for domain in domains if len(domain) == 1] is the same length:
    # Every domain has one possible value
    return (domain[0] for domain in domains)

  X = [X for X in domains if domain(X) > 1][0]
  D1, D2 = split(domain(X))

  domains1 = domains.replace(X, D1)
  domains2 = domains.replace(X, D2)

  todo = [(Z, C) for C in constraints if X in scope(C) and Z in scope(C) and X != Z]
  # Domain of X has been split so need to recheck all variables that are involved with constraints involving X

  return CSPSolver(variables, domains1, constraints, todo) or CSPSolver(variables, domains2, constraints, todo)

Variable Elimination

Eliminate variables one-by-one, passing constraints onto their neighbors.

Constraints can be thought of as a relation containing tuples for all possible valid values.

A join operation can be done on two constraints to capture both constraints, joining on the variable being eliminated. Then, this table can be projected with that column (variable being eliminated) removed.

Algorithm

def VE_CSP(variables, constraints):
  if len(variables) == 1:
    return join(...constraints)
  else:
    X = variables.pop() # select variable to eliminate and remove from variables
    constraints_X = [C for C in constraints if X in scope(C)]
    R = join(...constraints_X)
    R2 = project(all_variables_but_X)

    new_constraints = [C for C in constraints if X not in scope(C)] + R2
    recursed = VE_CSP(variables, new_constraints)
    return join(R, recursed)