Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

CS4402 - Constraint Programming

Practical 2: Constraint Solver Implementation

INTRODUCTION

A constraint satisfaction problem (CSP) requires a value, selected from a given nite domain, to be
assigned to each variable in the problem so that all constraints for the variables are satis ed.
Constraint satisfaction is a powerful technique to tackle a large number of problems in Arti cial
Intelligence and several other areas of Computer Science, from graph problems to scheduling and
timetabling problems and several other combinatorial problems in operational research.

For example, let's take a real-world problem where one needs an algorithm which matches infected
people and hospitals based on various criteria such as the age of the patient, severity of the illness,
location of the hospital, number of beds in a hospital etc. A popular choice would be to use deep
learning techniques and employ a neural network model with a broad range of parameters.
However, the cons to this would be that a model would rst need to be trained on historical data of
all hospitals and previous cases. This would also lead to substantial time in cleaning and
consolidating the data. Subsequently, several neural network architectures would have to be tested
for their accuracy and ef ciency. On the other hand, by modelling the problem in terms of it being a
CSP, the aforementioned downsides could be overcome. There would be no need for previous data
thus avoiding long training time and testing of various neural architectures.

In this practical, we are trying to programmatically produce a Constraint Solver to solve a CSP
using two popular algorithms:

1. Forward Checking

2. Maintaining Arc Consistency

The way in which the variables are ordered can in uence the solution of these solvers and
have a substantial impact on the complexity of the backtrack search. The ordering may be
either
1. Static ordering: The order of the variables is speci ed before the search begins, and it is not
changed thereafter, or
2. Dynamic ordering: The choice of the next variable to be considered at any point depends on
the current state of the search.
Hence, we are also going to witness these by taking into consideration the variable ordering. The
two types of variable ordering implemented in the algorithms are by ascending order of the variable
and smallest domain rst(SDF).

Once the decision is made to instantiate a variable, it may have several values available in its
domain. Again, the order in which these values are considered can have a substantial impact on the
time to nd the rst solution. In this practical, the implementation of the algorithms have been
defaulted to ascending value selection.

FORWARD CHECKING ALGORITHM

In a CSP, every variable can take a value in a nite domain. A backtracking algorithm could be used
to iteratively chooses a variable and test each of its possible values recursively. Look ahead is used
to check the effects of choosing a given variable to evaluate or to decide the order of values to give
fi
fi
fi
fi
fi
fi
fl
fi
fi
fi
fi
to it. The Forward Checking algorithm is a simple technique for evaluating the effect of a speci c
assignment to a variable. Considering the current partial solution and a candidate assignment to
evaluate, it checks whether another variable can take a consistent value.

• The implementation of the forward checking algorithm simply begins by initialising the variable
list which is retrieved from the CSP le and stored in a LinkedHashSet so that it is unique and
the order of the variables is preserved

LinkedHashSet<Integer> varList = new LinkedHashSet<Integer>(); // Variables list

• The domains of all these variables are retrieved from the CSP le and converted into a Hashmap
that maps each variable to its respective values it can take within a domain. This is de ned
globally in the class

HashMap<Integer, HashSet<Integer>> valueList; // all values that a variable can take

• The forwardCheck(LinkedHashSet<Integer> varList) is the entry point to the recursive algorithm


which rst checks if all the assignments to the variables have been completed, and if so it prints
the solution displaying the nodes count, count of the arc revisions made and each subsequent line
displaying the value of the variables as ordered.

• If all the assignments haven’t been made a variable is selected from the varList based on
ascending ordering or the smallest domain rst. The ascending ordering of variables follows the
same order as mentioned in the CSP. The SDF ordering technique selects the variable which has
the smallest domain at any given instance of the forwardCheck call.

• The value of the selected variable is then selected based on the ascending order as defaulted in
this practical.

• As all assignments happen on the left branch, we start by assuming an assignment for the variable
to check if all other future variables are going to be consistent moving forward.

• After the value assignment, the next step is to check if all future arcs are consistent. Hence, we
start by initialising:

boolean consistent = true;


ForEach variable in varList: if the variable is not equal to the currently assigned variable:
- Form the arc (variable, assigned var)
- ForEach constraint in constraints that belongs to the arc:
- ForEach tuple in the constraint:
- Prune the values of the future variable that don’t have any support
- Track the pruned values to restore them if the assignment doesn’t work
- while pruning keep track of domain wipeouts
- if (wipeout) set consistent = false; break
If (consistent): forwardCheck(varList - currently assigned variable)
Else if not consistent: undo the pruning done earlier and unassign the variable with that value

• At the time of pruning the prunedValues is a data structure that keeps track of all the value
deletions for the future variables
HashMap<Integer, HashSet> prunedValues = new HashMap<Integer, HashSet>();
Suppose a deletion of 1 and 2 were to occur from the valueList of variable 0, the prunedValues
would map 0 to {1, 2} in prunedValues
fi
fi
fi
fi
fi
fi
When we have to undo prune the valueList set and the prunedValues set can simply be restored by
doing a set addition.

• If a particular assignment for a variable has failed, its time to unassign the variable using the right
branch and the value would then be required to be removed from its domain which is maintained
in the valueList

• After deleting the variable from its domain we check for any possible domain wipeouts caused
due to this. If a domain wipeout has occurred due to this deletion then we simply add it back into
the variables domain
If no wipeout has occurred then we can proceed further to check future arc consistency.

• The checking of future arcs consistency works in a similar way to how it works on the left branch
and If (consistent): forwardCheck(varList)
Else we undo prune() which is the same as discussed above.

MAINTAINING ARC CONSISTENCY ALGORITHM

So Arc Consistency algorithms are responsible for making every constraint in a problem consistent.
A very naive algorithm for doing so might be as follows:

Naive Algorithm to achieving Arc Consistency - AC1

Loop forever:
ForEach constraint in constraints:
ForEach variable of that constraint:
ForEach value in the domain of that variable:
Search for support of that value on the constraint
If no support is found:
remove the value from the domain
If nothing has been removed in this loop, exit

In other words, this approach works, but it is extremely inef cient as you have to search everything
for a value to remove, and keep doing so until there’s nothing left. There may be many constraints,
and many values in each variable thus making this method very time-consuming. The AC3
algorithm overcomes the drawbacks of AC1 which is enforced in the MAC algorithm.
The basic idea of the MAC Algorithm is to maintain global arc consistency, hence, after each
assignment, MAC re-establishes global arc consistency. In comparison to forward checking the
MAC identi es dead-ends a lot sooner with little searching

MAC3 was implemented in the following way:

• The implementation of the MAC algorithm simply begins by initialising the variable list which
is retrieved from the CSP le and stored in a LinkedHashSet so that it is unique and the order of
the variables is preserved

LinkedHashSet<Integer> varList = new LinkedHashSet<Integer>(); // Variables list


fi
fi
fi
• The domains of all these variables are retrieved from the CSP le and converted into a Hashmap
that maps each variable to its respective values it can take within a domain. This is de ned
globally in the class

HashMap<Integer, HashSet<Integer>> valueList; // all values that a variable can take

• The mac3(LinkedHashSet<Integer> varList) is the entry point to the recursive algorithm. It starts
by selecting a variable from the varList based on ascending ordering or the smallest domain rst.
The ascending ordering of variables follows the same order as mentioned in the CSP.

• The value of the selected variable is then selected based on the ascending order as defaulted in
this practical.

• It then checks if all the assignments to the variables have been completed, and if so it prints the
solution displaying the nodes count, count of the arc revisions made and each subsequent line
displaying the value of the variables as ordered.

• If all the assignments haven’t been made yet it starts checking for global arc consistency after the
recently made assignment.

• This is where the MAC is different from the FC algorithm. It makes use of the AC3 to achieve
global arc consistency after each variable assignment.

Arc Consistency 3 - AC3

For i = 1 To n:
Q = all arcs(xi , xj ) where i != j
While Not empty(Q):
Remove the rst arc(xi , xj ) from the Q
If arc(xi , xj) leads to any revision due to pruning:
Add to Q all arcs(xh, xi ) (h != j)

• To achieve the above, I’ve used an ArrayList that behaves exactly like a queue in terms of its
operation of being FIFO. To an empty Q, I add every future incident arc of the current variable.

• While iterating until the queue is empty, I rst pop the arc to the front of the queue and check if
the arc is consistent just in the same way as done in forward checking. If a value has no support
then prune it from its domain and add it to the pruned list to restore later.

• If consistency is maintained until the Q is empty then we recursively call mac3(varList);


However, while pruning if a domain wipeout occurs then it's no longer going to be consistent
and would have to undo the pruning.
• The undo pruning works in the exact same way as in FC. At the time of pruning the
prunedValues is a data structure that keeps track of all the value deletions for the future variables
HashMap<Integer,HashSet> prunedValues = new HashMap<Integer,HashSet>();
Suppose a deletion of 1 and 2 were to occur from the valueList of variable 0, the prunedValues
would map 0 to {1, 2} in prunedValues
When we have to undo prune the valueList set and the prunedValues set can simply be restored
by doing a set addition.
fi
fi
fi
fi
fi
EMPIRICAL EVALUATION

Disclaimer: In spite of getting both algorithms working for all the problems for ascending variable
ordering, I’m failing to count the nodes and arcs correctly for the algorithms. But I’ve still tried to
document my results here. But I’m sure that the analysis of the algorithms doesn’t seem to be
correct.
4Queens.csp

Algorithm Variable Ordering Time Taken (ms) Nodes Arc revisions

FC ASC 0.487625 9 18

FC SDF 0.594917 9 18

MAC ASC 1.20084 8 18

MAC SDF 1.20085 8 18

6Queens.csp

Algorithm Variable Ordering Time Taken Nodes Arc revisions

FC ASC 1.85442 28 99

FC SDF 2.331792 28 99

MAC ASC 1.201088 27 99

MAC SDF 1.201088 27 99

8Queens.csp

Algorithm Variable Ordering Time Taken Nodes Arc revisions

FC ASC 4.379958 89 390

FC SDF 0.72375 9 28

MAC ASC 1.2011 88 390

MAC SDF 1.2011 8 28

10Queens.csp

Algorithm Variable Ordering Time Taken Nodes Arc revisions

FC ASC 5.379 84 430

FC SDF 0.945 11 45

MAC ASC 1.201 83 430

MAC SDF 1.201 10 45

langfords2_3.csp
Algorithm Variable Ordering Time Taken Nodes Arc revisions

FC ASC 1.4285 17 53
Algorithm Variable Ordering Time Taken Nodes Arc revisions

FC SDF 0.73233 11 26

MAC ASC 1.201 16 53

MAC SDF 1.201 10 26

CONCLUSION

Forward checking is the easiest way to prevent future con icts. Instead of performing arc
consistency to the assigned variables, it performs a restricted form of arc consistency to the
variables that haven’t been assigned. Meaning, when a value is assigned to the current variable, any
value in the domain of a future variable which con icts with this assignment is temporarily
removed from the domain. Therefore, if the domain of a future variable becomes empty, it is known
immediately that the current partial solution is inconsistent. Forward checking, therefore, allows
branches of the search tree that will lead to failure to be pruned earlier than with simple
backtracking. Improving on the forward-checking algorithm, it’s conclusive that full arc consistency
is a lot more ef cient in terms of making several assignments before realising that it may eventually
fail.
fi
fl
fl

You might also like