Notes on Chapter 6: Constraint Satisfaction Problems

constraint satisfaction problems, or CSPs for short, are a flexible approach to searching that have proven useful in many AI-style problems

CSPs can be used to solve problems such as

  • graph-coloring: given a graph, the a coloring of the graph means assigning each of its vertices a color such that no pair of vertices connected by an edge have the same color
    • in general, this is a very hard problem, e.g. determining if a graph can be colored with 3 colors is NP-hard
    • many problems boil down to graph coloring, or related problems
  • job shop scheduling: e.g. suppose you need to complete a set of tasks, each of which have a duration, and constraints upon when they start and stop (e.g. task c can’t start until both task a and task b are finished)
    • CSPs are a natural way to express such problems
  • cryptarithmetic puzzles: e.g. suppose you are told that TWO + TWO = FOUR, and each of the letters corresponds to a different digit from 0 to 9, and that a number can’t start with 0 (so T and F are not 0); what, if any, are the possible values for the letters?
    • while these are not directly useful problems, they are a simple test case for CSP solvers

the basic idea is to have a set of variables that can be assigned values in a constrained way

a CSP consists of three main components:

  • X: a set of variables {X1,,Xn}
  • D: a set of domains {D1,,Dn}, one domain per variable
    • i.e. the domain of Xi is Di, which means that Xi can only be assigned values from Di
    • we’re only going to consider finite domains; you can certainly have CSPs with infinite domains (e.g. real numbers), but we won’t consider such problems here
  • C is a set of constraints that specify allowable assignments of values to variables
    • for example, a binary constraint consists of a pair of different variables, (Xi,Xj), and a set of pairs of values that Xi and Xj can take on at the same time
    • we will usually only deal with binary constraints; constraints between three or more variables are possible (e.g. Xi,Xj,Xk are all different), but they don’t occur too frequently, and can be decomposed into binary constraints

example 1: suppose we have a CSP as follows:

  • three variables X1, X2, and X3

  • domains: D1={1,2,3,4}, D2={2,3,4}, D3={3,7}

    • so X1 can only be assigned one of the values 1, 2, 3, or 4
  • constraints: no pair of variables have the same value, i.e. X1X2, X1X3, and X2X3; we can explicitly describe each of these constraints as a relation between the two variables where the pairs show the allowed values that the variables can be simultaneously assigned, i.e.

    • X1X2={(1,2),(1,3),(1,4),(2,3),(2,4),(3,2),(3,4),(4,2),(4,3)}
    • X1X3={(1,3),(1,7),(2,3),(2,7),(3,7),(4,3),(4,7)}
    • X2X3={(2,3),(2,7),(3,7),(4,3),(4,7)}

    these three constraints are each binary constraints because they each constrain 2 variables

example 2: suppose we have the same variables and domains as in the previous example, but now the constraints are X1<X2 and X2+X35

  • domains: D1={1,2,3,4}, D2={2,3,4}, D3={3,7}
  • both of the constraints are binary constraints, because they each involve two variables
  • X1<X2={(1,2),(1,3),(1,4),(2,3),(2,4),(3,4)}
  • X2+X39={(2,3),(3,3)}

by looking at the constraints for this problem, we note the following:

  • the only possible value for X3 is 3, because 3 is the only value for X3 in the constraint X2+X39
  • we can also see the constraint X2+X39 that X2 cannot be 4; so in the constraint X1<X2 we can discard all pairs whose second value is 4, giving: X1<X2={(1,2),(1,3),(2,3)}
  • so the final solutions to this problem are: (X1=1,X2=1,X3=3), (X1=1,X2=3,X3=3), (X1=2,X2=3,X3=3)

example 3: suppose we have the same variables and domains as in the previous example, but now with the two constraints X1+X2=X3, and X1 is even

  • domains: D1={1,2,3,4}, D2={2,3,4}, D3={3,7}
  • X1+X2=X3 is a ternary constraint, because it involves 3 variables
  • X1+X2=X3={(1,2,3),(3,4,7),(4,3,7)}
  • X1 is even is a unary constraint, because it involves one variable
  • X1 is even ={2,4}
  • looking at the triples in X1+X2=X3, the only one where X_1 is even is (4,3,7), and so the only solution to his problem is (X1=4,X2=3,X3=7)

Some Terminology and Basic Facts

  • variables can be assigned, or unassigned
  • if a CSP has some variables assigned, and those assignments don’t violate any constraints, we say the CSP is consistent, or that it is satisfied
    • that partial sets of CSP variable can be satisfied is an important part of many CSP algorithms that work by satisfying one variable at a time
  • if all the variables of a CSP are assigned a value, we call that a complete assignment
  • a solution to a CSP complete assignment that is consistent, i.e. a complete assignment that does not violate any constraints
  • depending on the constraints, a CSP may have 0, 1, or many solutions
    • a CSP with no solutions is sometimes referred to as over-constrained
    • a CSP with many solutions is sometimes referred to as under-constrained
  • depending upon the problem being solved and its application, we may want just 1 solution, or we might want multiple solutions
    • for over-constrained CSPs, we may even be satisfied with a solution that violates the fewest constraints
  • if a domain is empty, then the CSP is no solution
  • if a domain has only one value, then that is the only possible value for the corresponding variable
  • the size of the search space for a CSP is |D1||D2||Dn|
    • this is the number of n-tuples that the CSP is searching through
    • this is often a quick and easy way to estimate the potential difficulty of a CSP: the bigger the search space, the harder the problem might be
      • this is just a rule of thumb: it’s quite possible that a problem with a large search space is easier than a problem with s small search space, perhaps because there are many more solutions to be found in the large search space

Constraint Propagation: Arc Consistency

as mentioned above, we are only considering CSPs with finite domains, and with binary constraints between variables

it turns out that many such CSPs can be translated into a simpler CSP that is guaranteed to have the same solutions

the idea we will consider here is arc consistency

the CSP variable Xi is said to be arc consistent with respect to variable Xj if for every value in Di there is at least one corresponding value in Dj that satisfies the binary constraint on Xi and Xj

an entire CSP is said to be arc consistent if every variable is arc consistent with every other variable

if a CSP is not arc consistent, then it turns out that there are relatively efficient algorithms that will make it arc consistent

  • and the resulting arc consistent CSP will have the same solutions as the original CSP, but often a smaller search space
  • for some problems, making a CSP arc consistent may be all that is necessary to solve it

the most popular arc consistency algorithm as AC-3

  • on a CSP with c constraints a maximum domain size of d, AC-3 runs in O(cd3) time
function AC-3(csp)
  returns: false is an inconsistency is found
           true otherwise
           csp is modified to be arc-consistent
  input: CSP with components (X, D, C)
  local variables: queue of arcs (edges) in
                   the CSP graph

  queue <-- all arcs in csp
  while queue is not empty do
    (X_i, X_j) <-- queue.pop_first()
    if Revise(csp, X_i, X_j) then
      if size(D_i) == 0 then return false
      for each X_k in neighbors(X_i) - {X_j} do
        add (X_k, X_i) to queue
      end for
    end if
  end while
  return true


function Revise(csp, X_i, X_j)
  returns true iff domain D_i has been changed

  revised <-- false
  for each x in D_i do
    if no value y in D_j allows (x,y) to satisfy
         the constraint between X_i and X_j then
      delete x from D_i
      revised <-- true
  end for
  return revised

example. Consider the following CSP with variables A, B, C, and D, and:

D_A = {1, 2, 3} D_B = {2, 4} D_C = {1, 3, 4} D_D = {1, 2}

A < B A < D B = D B != C C != D

it’s useful to draw the corresponding constraint graph, and then to apply AC-3 to that by hand to see how the domains change

in this particular problem, AC-3 reduces 3 of the 3 domains down to a single value

here’s an implementation using the csp.py program from the textbook:

import csp

vbls  = ['A', 'B', 'C', 'D']
doms  = {'A':[1,2,3], 'B':[2,4], 'C':[1,3,4], 'D':[1,2]}
neigh = {'A':['B','D'], 'B':['A','C','D'],
         'C':['B','D'], 'D':['A','B','C']}
cnstr = {
  ('A','B'): lambda x,y: x < y,
  ('B','A'): lambda x,y: x > y,

  ('A','D'): lambda x,y: x < y,
  ('D','A'): lambda x,y: x > y,

  ('B','D'): lambda x,y: x == y,
  ('D','B'): lambda x,y: x == y,

  ('B','C'): lambda x,y: x != y,
  ('C','B'): lambda x,y: x != y,

  ('C','D'): lambda x,y: x != y,
  ('D','C'): lambda x,y: x != y,
}

# If X=a and Y=b satisfies the constraints on X and Y, then True is returned.
# Otherwise,. False is returned.
def constraints(X, a, Y, b):
  try:
    return cnstr[(X,Y)](a,b)
  except KeyError:  # if a pair is not in the table, then there are no constraints
    return True     # on X an Y, and so X=a and Y=b is acceptable


prob = csp.CSP(vbls, doms, neigh, constraints)

if __name__ == '__main__':
  print('Problem domains before AC3 ...')
  print(prob.domains)

  csp.AC3(prob)

  print()
  print('Problem domains after AC3 ...')
  print(prob.curr_domains)

Local Search: Min-conflicts

backtracking search solves a CSP by assigning one variable at a time

another approach to solving a CSP is to assign all the variables, and then modify this assignment to make it better

this is a kind of local search on CSPs, and for some problems it can be extremely effective

example: 4-queens problem

  • place 4 queens randomly on a 4-by-4 board, one queen per column
  • pick a queen, and re-position it in its column so that it is attacking the fewest number of other queens; this is know as the min-conflicts heuristic
  • repeat the previous step until there are no possible moves that reduce conflicts; if the puzzle is solved, then you’re done; if it’s not solved, then re-start the entire process with a new initial random placement of queens

local search using min-conflicts can be applied to any CSP as follows:

function Min-Conflicts(csp, max_steps)
    returns: solution, or failure
    Inputs: csp, a constraint satisfaction problem
            max_steps, number of steps allowed before giving up

current <-- an initial complete assignment for CSP
for i = 1 to max_steps do
  if current is a solution, then return current
  var <-- randomly chosen conflicted variable from csp.VARIABLES
  value <-- the value v for var that minimizes CONFLICTS(var, v, current, csp)
  set var = value in current
end

the CONFLICTS functions returns the count of the number of variables in the rest of the assignment that violate a constraint when var=v

tailored version of Min-Conflicts can solve the n-queens problem for n=3 million in less than a minute (on 1990s computers!)

  • this is was a surprise — it’s not clear that min-conflicts will work so well on this problem

we will not cover anything from section 6.5