cs0311003/CoRR.tex
1: % november 2000. Generate and test version
2: % the revision submitted on november 27
3: %revision2 worked on in august 2002
4: %revision 3 worked on in June 2003
5: \documentclass{tlp}
6: 
7: \usepackage{aopmath}
8: 
9: 
10: \newcommand{\barp}{\ensuremath{I_P}}
11: \newtheorem{algorithm}{Algorithm}
12: 
13: \begin{document}
14: 
15: \bibliographystyle{acmtrans} 
16: 
17: 
18: \title[Intelligent Backtracking] 
19: {Enhancing a Search Algorithm to Perform Intelligent Backtracking} 
20: 
21: \author[Maurice Bruynooghe]
22: {MAURICE BRUYNOOGHE \\
23: Katholieke Universiteit Leuven, Department of Computer
24:   Science
25: \\Celestijnenlaan 200A, B3001 Heverlee, Belgium\\
26:  e-mail: Maurice.Bruynooghe@cs.kuleuven.ac.be
27: }
28: \pagerange{\pageref{firstpage}--\pageref{lastpage}}
29: \volume{\textbf{1} (1):}
30: \jdate{January 2001}
31: \setcounter{page}{1}
32: \pubyear{2001}
33: 
34: 
35: \maketitle[p]
36: 
37: \shorttitle{Programming pearl}
38: 
39: 
40: \label{firstpage}
41: 
42: \begin{abstract}
43:   This paper illustrates how a Prolog program, using chronological
44:   backtracking to find a solution in some search space, can be
45:   enhanced to perform intelligent backtracking. The enhancement
46:   crucially relies on the impurity of Prolog that allows a program to
47:   store information when a dead end is reached. To illustrate the
48:   technique, a simple search program is enhanced.
49: 
50: To appear in Theory and Practice of Logic Programming.
51: 
52: \end{abstract}
53: 
54: \begin{keywords}
55:  intelligent backtracking, dependency-directed
56:   backtracking, backjumping, conflict-directed backjumping, nogood
57:   sets, look-back.  
58: \end{keywords}
59: 
60: \section{Introduction}
61: \label{sec:intro}
62: 
63: The performance of backtracking algorithms for solving finite-domain
64: constraint satisfaction problems can be improved substantially by so
65: called look-back and look-ahead methods \cite{Dechter02}. Look-back
66: techniques extract information by analyzing failing search paths that
67: are terminated by dead ends and use that information to prune the
68: search tree. Look-ahead techniques use constraint propagation
69: algorithms in an attempt to avoid such dead ends altogether.
70: Constraint propagation can rather easily be isolated from the search
71: itself and can be localized in a constraint store. Following the
72: seminal work of \cite{Hentbook}, look-ahead techniques are available
73: to the logic programmer in a large number of systems.
74: 
75: This is not the case for look-back methods.  Intelligent backtracking
76: has been explored as a way of improving the backtracking behavior of
77: logic programs \cite{BP84}.  For some time, a lot of effort went into
78: adding intelligent backtracking to Prolog implementations (see
79: references in \cite{Br91}). However, the inherent space and time
80: costs, which must be paid even when no backtracking occurs, impeded
81: its introduction in real implementations.
82: 
83: 
84: For a long time, look-ahead methods dominated in solving constraint
85: satisfaction problems. However, already in \cite{RB86} we have shown
86: empirical evidence that look-back methods can be useful, even that it
87: can be interesting to combine both. Starting in the nineties there is
88: a renewed interest in look-back methods, {\em e.g.}, \cite{ginsberg93},
89: and in combining look-back with look-ahead {\em e.g.},
90: \cite{Dechter02}.
91: 
92: 
93: Look-back turned out to be the most successful of the approaches tried 
94: in a research project aiming at detecting unsolvable queries (queries
95: that do not terminate, such as the query $\leftarrow \mathit{ odd}(X),
96: \mathit{ even}(X)$ for a program defining odd and even numbers). The
97: approach was to construct a model of the program over a finite domain
98: in which the query was false. The central part of this model
99: construction was to search for a pre-interpretation leading to the
100: desired model, {\em i.e.}, with $D$ the domain, to find an appropriate
101: function $D^n \rightarrow D$ for every n-ary functor in the program. A
102: meta-interpreter was built which performed a backtracking search over
103: the solution space. A control strategy was devised which resulted in
104: the early detection of instances of program clauses which showed that
105: the choices made so far could not result in the desired model. This
106: meta-interpreter outperformed dedicated model generators on several
107: problems \cite{BVWD98}. However it remained very sensitive to the
108: initial ordering in which the various components of the different
109: functions were assigned. The point was that not all choices made so
110: far necessarily contributed to the evaluation of a clause instance.
111: We experimented with constraint techniques and also investigated the
112: use of intelligent backtracking. With a small programming effort, we
113: could enhance the meta-interpreter to support a form of intelligent
114: backtracking. As reported in \cite{BVWD99}, this was the most
115: successful approach. As Prolog is a popular tool for prototyping
116: search problems and as look-back methods, though useful, are not
117: available in off-the-shelf Prolog systems, we decided to describe for
118: a wider audience how to enhance a Prolog search program with a form of
119: intelligent backtracking. The technique crucially depends on the
120: impure feature of Prolog (assert/retract) that allows storing
121: information when a dead end is reached. The stored information is used
122: to decide whether a choice point should be skipped when chronological
123: backtracking returns to it. Hence we propose the technique as a black
124: pearl.
125: 
126: In the application mentioned above, the meta-interpreter is performing
127: a substantial amount of computation after making a choice whereas the
128: amount of computation added to support intelligent backtracking is
129: comparatively small. This is not always the case. When the amount of
130: computation in between choices is small and solutions are rather easy
131: to find, the overhead of supporting intelligent backtracking may be
132: larger than the savings due to the pruning of the search space. This
133: is the case in toy problems such as the n-queens. In the example we
134: develop here, there is a small speed-up.
135: 
136: 
137: We recall some basics of intelligent
138: backtracking in Section~\ref{sec:IB}. In Section~\ref{sec:nqueens}, we
139: introduce the example program and in Section \ref{sec:ib} we enhance
140: it with intelligent backtracking. We conclude with a discussion in
141: Section~\ref{sec:discussion}. 
142: 
143: \section{Intelligent Backtracking}
144: \label{sec:IB}
145: 
146: Intelligent backtracking as described in \cite{Br81} is a very general
147: schema. It keeps track of the reason for eliminating a variable in a
148: domain. Upon reaching a dead end, it identifies a culprit for the
149: failure and {\em jumps back} to the choice point where the culprit was
150: assigned a value. Information about the variables assigned in between
151: the culprit and the dead end can be retained if still valid, as in the
152: dynamic backtracking of \cite{ginsberg93} which can be considered as
153: an instance of the schema. More straightforward in a Prolog
154: implementation is to give up that information, this gives the
155: backjumping algorithm (Algorithm 3.3) in \cite{ginsberg93}
156: (intelligent backtracking with static order in \cite{RB86}). We follow
157: rather closely \cite{ginsberg93} for introducing it.
158: 
159: A constraint satisfaction problem (CSP) can be identified by a triple
160: $(I,D,C)$ with $I$ a set of variables, $D$ a mapping from variables to
161: domains and $C$ a set of constraints. Each variable $i \in I$ is
162: mapped by $D$ into a domain $D_i$ of possible values.  Each constraint
163: $c \in C$ defines a relation $R_c$ over a set $I_c \subseteq I$ of
164: variables and is satisfied for the tuples in that relation.  A
165: solution to a CSP consists of a value $v_i$ (an {\em assignment}) for
166: each variable $i$ in $I$ such that: (1) for all variables $i$: $v_i
167: \in D_i$ and, (2) for all constraints $c$: with $I_c = \{j_1, \ldots,
168: j_k\} $, it holds that $(v_{j_1}, \ldots, v_{j_k}) \in R_c $.
169: 
170: A partial solution to a CSP $(I,D,C)$ is a subset $J \subseteq I$ and
171: an assignment to each variable in $J$. A partial solution $P$ is
172: ordered by the order in which the algorithm that computes it assigns
173: values to the variables and is denoted by a sequence of ordered pairs
174: $(i,v_i)$. A pair $(i,v_i)$ indicates that variable $i$ is assigned
175: value $v_i$; $\barp = \{i | (i,v_i) \in P\}$ denotes the set of
176: variables assigned values by $P$.
177: 
178: Given a partial solution $P$, an {\em eliminating explanation}
179: (cause-list in \cite{Br81}) for a variable $i$ is a pair $(v_i,S)$
180: where $v_i \in D_i$ and $S \subseteq \barp$. It expresses that the
181: assignments to the variables of $S$ by the partial solution $P$ cannot
182: be extended into a solution where variable $i$ is assigned value
183: $v_i$.  Contrary to \cite{ginsberg93}, we use an {\em elimination
184:   mechanism} that tests one value at a time. Hence we assume a
185: function $\mathit{ consistent}(P, i ,v_i)$ that returns true when $P
186: \cup \{(i,v_i)\}$ satisfies all constraints over $\barp \cup \{i\})$
187: and a function $\mathit{ elim}(P, i ,v_i)$ that returns an eliminating
188: explanation $(v_i,S)$ when $\neg\mathit{ consistent}(P, i ,v_i)$.
189: 
190: 
191: Below, we formulate the backjumping algorithm; next we clarify its
192: reasoning.  $E_i$ is the set of eliminating explanations for
193: variable $i$.
194: 
195: \begin{algorithm}
196: \label{alg:1}
197:   Given as inputs a CSP $(I,D,C)$.
198:   \begin{enumerate}
199:   \item Set $P:=\emptyset$.
200:   \item If $\barp = I$ return $P$. Otherwise select a variable $i \in I
201:     \setminus \barp$, set $S_i := D_i$ and $E_i := \emptyset$.
202:   \item If $S_i$ is empty then go to step 4; otherwise, remove an element
203:     $v_i$ from it.\\ 
204:     If $\mathit{ consistent}(P, i ,v_i)$ then extend $P$ with
205:     $(i,v_i)$ and go to step 2; otherwise add $\mathit{ elim}(P, i
206:     ,v_i)$ to $E_i$ and go to step 3.
207:   \item ($S_i$ is empty and $E_i$ has an eliminating explanation for
208:     each value in $D_i$.) Let $C$ be the set of all variables appearing
209:     in the explanations of $E_i$.
210:   \item If $C = \emptyset$, return failure. Otherwise, let $(l,v_l)$
211:     be the last pair in $P$ such that $l \in C$. Remove from $P$ this
212:     pair and any pair following it. Add $(v_l,C \setminus \{l\})$ to
213:     $E_l$, set $i:=l$ and go to step 3.
214:   \end{enumerate}
215: \end{algorithm}
216: 
217: 
218: In step 3, when the extension of the partial solution is inconsistent
219: then $\mathit{ elim}(P, i ,v_i)$ returns a pair
220: $(v_i,\{j_1,\ldots,j_m\})$ such that the partial solution
221: $(j_1,v_{j_1}), \ldots ,(j_m,v_{j_m}),(i,v_i) $ violates the
222: constraints. The inconsistency of this assignment can be expressed by
223: the clause: $\leftarrow j_1 = v_{j_1}, \ldots ,j_m=v_{j_m}, i=v_i
224: $ (The head is false, the body is a conjunction).
225: 
226: In step 4, when $S_i$ is empty, we have an eliminating explanation for
227: each value $v_{i_k}$ in the domain $D_i$. Hence we have a set of
228: clauses of the form
229: \begin{equation}
230:   \label{cl2}
231:   \leftarrow j_{k,1} = v_{j_{k,1}}, \ldots, 
232:   j_{k,m_k} = v_{j_{k,m_k}}, i=v_{i_k}
233: \end{equation}
234: 
235: The condition that the variable $i$ must be assigned a value from
236: domain $D_i$ with $n$ elements can be expressed by the clause (the
237: head is a disjunction, the body is true):
238: \begin{equation}
239:   \label{cl1}
240:   i =v_{i_1}  ,\ldots, i =v_{i_n} \leftarrow
241: \end{equation}
242: 
243: Now, one can perform hyperresolution \cite{Rob65} between clause
244: (\ref{cl1}) and the clauses of the form (\ref{cl2}) (for $k$ from 1 to
245: $n$). This gives:
246: \begin{equation}
247:   \label{cl4}
248:   \leftarrow j_{1,1} = v_{j_{1,1}}, \ldots, 
249:   j_{1,m_1} = v_{j_{1,m_1}}, \ldots,
250:   j_{n,1} = v_{j_{n,1}}, \ldots ,
251:   j_{n,m_n} = v_{j_{n,m_n}}
252: \end{equation}
253: 
254: 
255: This expresses a conflict between the current values of the variables
256: in the set $\{j_{1,1}, \ldots, j_{1,m_1}, \ldots, j_{n,1},
257: \ldots,j_{n,m_n} \} = C$. Hence, with $l$ the last assigned variable
258: in $C$, $C\setminus \{l\}$ is an eliminating explanation for
259: $v_l$.  The conflict $C$ is computed in step 4. When empty, the
260: problem has no solution as detected in step 5. Otherwise, step 5
261: backtracks and adds the eliminating explanation $(v_l, C\setminus
262: \{l\})$ to the set of eliminating explanations of variable $l$.  
263: 
264: 
265: 
266: One can observe that the algorithm does not use the individual
267: eliminating explanations in the set $E_i= (v_{i_k}, S_k)$, but only
268: the set $C$ which is the union of the sets $S_k$. As we have no
269: interest in introducing more refined forms of intelligent
270: backtracking, we develop Algorithm~\ref{alg:2} where $E_i$ holds the
271: union of the sets $S_k$ in the eliminating explanations of variable
272: $i$. To obtain an algorithm that closely corresponds to the Prolog
273: encoding we present in Section~\ref{sec:ib}, we reorganise the code
274: and introduce some more changes. The function $\mathit{
275:   elim}(P,i,v_i)$ that returns an eliminating explanation $(v_i,S)$
276: for the current value of variable $i$ is replaced by a function
277: $\mathit{ conflict}(P,i,v_i)$ that returns the set $\{i\} \cup S$ (the
278: variables that participate in a conflict as represented by
279: Equation~\ref{cl2}). This conflict is stored in a variable $C$ (step 3
280: of Algorithm~\ref{alg:2}). It is nonempty and $i$ is the last assigned
281: variable, hence the value of $i$ remains unchanged in step 4 and, in
282: step 5, the eliminating explanation $C \setminus \{i\}$ is added to
283: $E_i$. This reorganisation of the code has as result that a local
284: conflict (the chosen value for the last assigned variable $i$ is
285: inconsistent with the partial solution) and a deep conflict (all
286: values for variable $i$ have been eliminated) are handled in a uniform
287: manner: upon failure, the algorithm computes a conflict and stores it
288: in variable $C$ (for the local conflict in step 3, for the deep
289: conflict in step 5), backtracks to the variable computed in step 4
290: (the ``culprit'') and resumes in step 5 with updating $E_i$ and trying
291: a next assignment to variable $i$.  
292: 
293: 
294: \begin{algorithm}
295: \label{alg:2}
296:   Given as input a CSP $(I,D,C)$.
297:   \begin{enumerate}
298:   \item Set $P:=\emptyset$.
299:   \item If $\barp = I$ return P. Otherwise select a variable $i \in I
300:     \setminus \barp$. Select a value $v_i$ from $D_i$. Set $S_i := D_i
301:     \setminus \{v_i\}$ and $E_i := \emptyset$.
302:   \item If $\mathit{consistent}(P, i ,v_i)$ then extend $P$ with
303:     $(i,v_i)$ and go to step 2; otherwise set $C := \mathit{conflict}(P, i
304:     ,v_i)$. 
305:   \item If $C=\emptyset$ then return failure; otherwise let $(l,v_l)$
306:     be the last pair in $P$ such that $l \in C$. Set $i:=l$. 
307:   \item Add $C \setminus \{i\}$ to $E_i$. If $S_i= \emptyset$ then $C
308:     := E_i$ and go to step 4; otherwise select and remove a value
309:     $v_i$ from $S_i$ and go to step 3.
310:   \end{enumerate}
311: \end{algorithm}
312: 
313: 
314: \section{A search problem}
315: \label{sec:nqueens}
316: 
317: The code below is, apart from the specific constraints, fairly
318: representative for a finite domain constraint satisfaction problem.
319: The problem is parameterized with two cardinalities: {\tt VarCard},
320: the number of variables (the first argument of {\tt problem/3}) and
321: {\tt ValueCard}, the number of values in the domains of the variables
322: (the second argument of {\tt problem/3}). The third argument of {\tt
323:   problem/3} gives the solution in the form of a list of elements
324: $\mathit{ assign}(i,v_i)$. The main predicate uses {\tt
325:   init\_domain/2} to create a domain $[1, 2, \ldots,
326: \mathit{ValueCard}]$ and {\tt init\_pairs/3} to initialize
327: $\mathit{Pairlist}$ as a list of pairs $i$-$D_i$ with $D_i$ the domain
328: of variable $i$. The first argument of {\tt extend\_solution/3} is a
329: list of pairs $i$-$D_i$ with $i$ an unassigned variable and $D_i$ what
330: remains of its domain; the second argument is the (consistent) partial
331: solution (initialized as the empty list) and the third argument is the
332: solution. The predicate is recursive; each iteration extends the
333: partial solution with an assignment to the first variable on the list
334: of variables to be assigned. The nondeterministic predicate {\tt
335:   my\_assign/2} selects the value. If desirable, one could introduce a
336: selection function which dynamically selects the variable to be
337: assigned next.
338: 
339: Consistency of the new assignment with the partial solution is tested
340: by the predicates {\tt consistent1/2} and {\tt consistent2/2}. They
341: create a number of binary constraints. The binary constraints
342: themselves are tested with the predicates {\tt constraint1/2} and {\tt
343:   constraint2/2}. What they express is not so important. The purpose
344: is to create a problem that is sufficiently difficult so that
345: enhancing the program with intelligent backtracking pays off. For the
346: interested reader, the predicate {\tt consistent2/2} creates a very
347: simple constraint that verifies (using {\tt constraint1/2}) that the
348: value of the newly assigned variable is different from the value of
349: the previously assigned variable. The predicate {\tt consistent1/2}
350: creates a set of more involved constraints. The odd numbered and even
351: numbered variables each encode the constraints of the n-queens
352: problem. As a result, the solution of {\em e.g.,} {\tt problem(16,8,S)}
353: contains a solution for the 8-queens problem in the odd numbered
354: variables and a {\em different} (due to the constraints created by {\tt
355:   consistent2/2}) solution in the even numbered variables. Substantial
356: search is required to find a first solution.  For example, the first
357: solution for {\tt problem(16,8,S)} is found after 32936 assignments
358: (using a similar set-up of constraints, a solution is found for the
359: 8-queen problem after only 876 assignments).
360: 
361: Note that the constraint checking between the new assigned variable
362: and the other assigned variables is done in an order that is in
363: accordance with the order of assigning variables. Hence {\tt
364:   consistent1/2} is not tail recursive.  The order is not important
365: for the algorithm without intelligent backtracking. However, it is
366: crucial to obtain optimal intelligent backtracking: as with
367: chronological backtracking, constraint checking will stop at the first
368: conflict detected and an eliminating explanation will be derived from
369: it. As an eliminating explanation with an older assigned variable
370: gives more pruning than one with a more recently assigned variable,
371: the creation of constraints requires one to pay attention to the
372: order. It is done already here to minimize the differences between
373: this version and the enhanced version.
374: 
375: \begin{verbatim} 
376: problem(VarCard,ValueCard,Solution) :-
377:         init_domain(ValueCard,Domain),
378:         init_pairs(VarCard,Domain,Pairs),
379:         extend_solution(Pairs,[],Solution).
380: 
381: init_domain(ValueCard,Domain) :-
382:         ( ValueCard=0 -> Domain=[]
383:         ; ValueCard>0, ValueCard1 is ValueCard-1, 
384:             Domain=[ValueCard|Domain1],
385:             init_domain(ValueCard1,Domain1)
386:         ).
387: 
388: init_pairs(VarCard,Domain,Vars) :-
389:         ( VarCard=0 -> Vars = []
390:         ; VarCard>0, VarCard1 is VarCard-1,
391:             Vars=[VarCard-Domain|Vars1],
392:             init_pairs(VarCard1,Domain,Vars1)
393:         ).
394: 
395: extend_solution([],Solution,Solution).
396: extend_solution([Var-Domain|Pairs],PartialSolution,Solution) :-
397:         my_assign(Domain,Value),
398:         consistent1(PartialSolution,assign(Var,Value)),
399:         consistent2(PartialSolution,assign(Var,Value)),
400:         extend_solution(Pairs,
401:                        [assign(Var,Value)|PartialSolution],
402:                        Solution).
403: 
404: my_assign([Value|_],Value).
405: my_assign([_|Domain],Value) :- my_assign(Domain,Value).
406: 
407: consistent1([],_).
408: consistent1([_],_).
409: consistent1([_, Assignment1|PartialSolution],Assignment0) :- 
410:         consistent1(PartialSolution,Assignment0),
411:         constraint1(Assignment0,Assignment1),
412:         constraint2(Assignment0,Assignment1).
413: 
414: consistent2([],_).
415: consistent2([Assignment1|_],Assignment0) :- 
416:         constraint1(Assignment0,Assignment1).
417: 
418: constraint1(assign(_,Value0),assign(_,Value1)) :- Value0 \== Value1.
419: 
420: constraint2(assign(Var0,Value0),assign(Var1,Value1)) :-
421:         D1 is abs(Value0-Value1),
422:         D2 is abs(Var0-Var1)//2,
423:         D1 \== D2.
424: \end{verbatim}
425: 
426: 
427: 
428: \section{Adding intelligent backtracking}
429: \label{sec:ib}
430: 
431: Adding intelligent backtracking requires us to maintain eliminating
432: explanations. In Algorithm~\ref{alg:2}, a single eliminating
433: explanation is associated with each variable. The eliminating
434: explanation of a variable $i$ is initialised as empty in step 2, when
435: assigning a first value to the variable. It is updated in step 5, when
436: the last assigned value turns out to be the ``culprit'' of an
437: inconsistency.  This happens just before assigning the next value to
438: variable $i$. This indicates that the right place to store eliminating
439: explanations is as an extra argument in the predicate {\tt
440:   my\_assign/2}. In step 4, the algorithm has to identify the ``last''
441: variable $l$ of a conflict (the ``culprit''), just before updating the
442: eliminating explanation. We will also use the {\tt my\_assign/2}
443: predicate to check whether the variable it assigns corresponds to the
444: culprit of the failure.  Hence also the identitity of the variable
445: should be an argument. These considerations lead to the replacement of
446: the {\tt my\_assign/2} predicate by the following {\tt my\_assign/4}
447: predicate.
448: 
449: 
450: \begin{verbatim}
451: my_assign([Value|_],_Var,_Explanation,Value ).
452: my_assign([_|Domain],Var,Explanation0,Value) :-
453:         get_conflict(Conflict),
454:         remove(Var,Conflict,Explanation1),
455:         set_union(Explanation0,Explanation1,Explanation),
456:         my_assign(Domain,Var,Explanation,Value).
457: my_assign([],_Var,Explanation,_Value) :-
458:         save_conflict(Explanation), fail.
459: \end{verbatim}
460: 
461: \noindent
462: 
463: It is called from {\tt extend\_solution/4} as {\tt
464:   myassign(Domain,Var,[],Value)} (what remains of the domain is the
465: first argument, the second argument is the variable being assigned,
466: the third argument is the initially empty eliminating explanation and
467: the fourth argument returns the assigned value). The initial call
468: together with the base case perform the otherwise branch of step 2.
469: The second clause, entered upon backtracking when the domain is
470: nonempty, checks whether the variable being assigned is the culprit.
471: To do so, it needs the conflict. As this information is computed just
472: before failure occurs, it cannot survive backtracking when using the
473: pure features of Prolog. One has to rely on the impure features for
474: asserting/updating clauses. Either {\tt assert/1} and {\tt retract/1}
475: or more efficient variants of specific Prolog systems\footnote{In our
476:   experiments, we made use of SICStus Prolog and employed {\tt
477:     bb\_put/2} and {\tt bb\_get/2}.}. The call to {\tt
478:   get\_conflict(Conflict)} picks up the saved conflict\footnote{We
479:   implemented it as {\tt get\_conflict(Conflict) :-
480:     bb\_get(conflict,Conflict)}.}; next, the call {\tt
481:   remove(Var,Conflict,Explanation1)} checks whether {\tt Var} is part
482: of it. If not, {\tt my\_assign/4} fails and backtracking returns to the
483: previous assignment. If {\tt Var} is the culprit, then the code
484: performs step 5 of the algorithm: {\tt remove/3} returns the
485: eliminating explanation in its third argument, {\tt set\_union/3} adds
486: it to the current eliminating explanation and the recursive call
487: checks whether the domain is empty. If not, the base case of {\tt
488:   my\_assign/4} assigns a new value. If the domain is empty, then the
489: last clause is selected. The eliminating explanation becomes the
490: conflict and is saved with the call to {\tt
491:   save\_conflict(Explanation)} that relies on the impure
492: features\footnote{We implemented it as {\tt save\_conflict(Conflict)
493:     :- bb\_put(conflict,Conflict)}.} and the clause fails.
494: 
495: 
496: Further modifications are in the predicates {\tt constraint1/2} and
497: {\tt constraint2/2} that perform the constraint checking. If a
498: constraint fails, the variables involved in it make up the conflict
499: and have to be saved so that after re-entering {\tt myassign/4} the
500: conflict can be picked up and used to compute an eliminating
501: explanation (step 3).  As the last assigned variable participates in all
502: constraints, it is part of the conflict.  For example, the code for
503: {\tt constraint1/2} becomes:
504: 
505: \begin{verbatim}
506: constraint1(assign(Var0,Value0),assign(Var1,Value1)) :-
507:         ( Value0 \== Value1 -> true
508:         ; save_conflict([Var0,Var1]), fail
509:         ).
510: \end{verbatim}
511: 
512: The modification to {\tt constraint2/2} is similar.  Recall that the
513: order in which constraints are checked determines the amount of
514: pruning that is achieved.  Finally, if one is interested in more than
515: one solution then also a conflict has to be stored when finding a
516: solution. It consists of all variables making up the solution. Using a
517: predicate {\tt allvars/2} that extracts the variables from a solution,
518: the desired behavior is obtained as follows:
519: \begin{verbatim}
520: problem(VarCard,ValueCard,Solution) :-
521:         init_domain(ValueCard,Domain),
522:         init_pairs(VarCard,Domain,Pairs),
523:         extend_solution(Pairs,[],Solution),
524:         initbacktracking(Solution).
525: 
526: initbacktracking(Solution) :-
527:         allvars(Solution,Conflict),
528:         save_conflict(Conflict).
529: \end{verbatim}
530: 
531: 
532: The enhanced program generates the same solutions as the original, and
533: in the same order. For {\tt problem(16,8,S)} the number of assignments
534: goes down from 32936 to 4015 and the execution time from 140ms to
535: 70ms; for {\tt problem(20,10,S)}, the reduction is respectively from
536: 75950 to 15813 and from 370ms to 310ms. The achieved pruning more than
537: compensates for the (substantial) overhead of recording and updating
538: conflicts\footnote{Using {\tt bb\_get} and {\tt bb\_put} to count the
539:   number of assignments increases execution time of the initial
540:   algorithm for {\tt problem(16,8,S)} from 140ms to 400ms.}  and of
541: the calls to {\tt remove/3} and {\tt set\_union/3}. Note that the
542: speed-up decreases with larger instances of this problem. This is
543: likely due to the increasing overhead of the latter two predicates.
544: Keeping the conflict set sorted (easy here because the variable
545: numbers corresponds with the order of assignment) such that the
546: culprit is always the first element could reduce that overhead.
547: 
548: \section{Discussion}
549: \label{sec:discussion}
550: 
551: In this black pearl, we have illustrated by a simple example how a
552: chronological backtracking algorithm can be enhanced to perform
553: intelligent backtracking. As argued in the introduction, look-back
554: techniques are useful in solving various search problems. Hence
555: exploring their application can be very worthwhile when building a
556: prototype solution for a problem. The technique presented here
557: illustrates how this can be realized with a small effort when
558: implementing a prototype in Prolog. Interestingly, the crucial feature
559: is the impurity of Prolog that allows the search to transfer
560: information from one point in the search tree (a dead end) to another.
561: It illustrates that Prolog is a multi-faceted language. On the one
562: hand it allows for pure logic programming, on the other hand it is a
563: very flexible tool for rapid prototyping.  Note that the savings due
564: to the reduction of the search space could be undone by the overhead
565: of computing and maintaining the extra information, especially, when
566: the amount of computation between two choice points is small.
567: 
568: The combination of look-back and look-ahead techniques can be useful,
569: and algorithms integrating both can be found, {\em e.g.},
570: \cite{Dechter02}. The question arises whether our solution can be
571: extended to incorporate look-ahead. This requires some work, however,
572: much of the design can be preserved. The initialization ({\tt
573:   init\_domains/3}) should not only associate variables with their initial
574: finite domain, but also with their eliminating explanations (initially
575: empty). Then the code for the main iteration could be as follows:
576: \begin{verbatim} 
577: extend_solution([],Solution,Solution).
578: extend_solution(Vars,PartialSolution,Solution) :-
579:         selectbestvar(Vars,var(Var,Values,Explanation),Rest),
580:         myassign(Values,Var,Explanation,Value),
581:         consistent(PartialSolution,assign(Var,Value)),
582:         propagate([assign(Var,Value)|PartialSolution],
583:                   NewPartialSolution)
584:         extend_solution(Vars,NewPartialSolution,Solution).
585: \end{verbatim} 
586: 
587: \noindent
588: The predicate {\tt selectbestvar/3} is used to dynamically select the
589: next variable to assign. It returns the identity of the variable
590: ($\mathit{ Var}$), the available values ($\mathit{ Values}$) and the
591: explanation ($\mathit{ Explanation}$) for the eliminated values. When
592: a partial solution is successfully extended, the predicate {\tt
593:   propagate/2} has to take care of the constraint propagation:
594: eliminating values from domains and updating the corresponding
595: explanations after which the next iteration can start. Computing the
596: eliminating explanation for each eliminated value requires great care
597: and depends on the kind of look-ahead technique used. It is pretty
598: straightforward for forward checking but requires careful analysis in
599: case of {\em e.g.}, arc consistency as no pruning will occur on
600: backjumping when the elimination is attributed to {\em all} already
601: assigned variables.
602: 
603: \section*{Acknowledgments}
604: 
605: I am grateful to Bart Demoen, Gerda Janssens and Henk Vandecasteele
606: for useful comments on various drafts of this pearl. I am very
607: grateful to the reviewers. Indeed, as often is the case, their
608: persistence and good advise greatly contributed to the clarity of the
609: exposition.
610: 
611: 
612: %\bibliography{pearl}
613: 
614: \begin{thebibliography}{}
615: 
616: \bibitem[\protect\citeauthoryear{Bruynooghe}{Bruynooghe}{1981}]{Br81}
617: {\sc Bruynooghe, M.} 1981.
618: \newblock {S}olving combinatorial search problems by intelligent backtracking.
619: \newblock {\em Information Processing Letters\/}~{\em 12,\/}~1, 36--39.
620: 
621: \bibitem[\protect\citeauthoryear{Bruynooghe}{Bruynooghe}{1991}]{Br91}
622: {\sc Bruynooghe, M.} 1991.
623: \newblock Intelligent backtracking revisited.
624: \newblock In {\em Computational Logic, Essays in Honor of Alan Robinson},
625:   {J.-L. Lassez} {and} {G.~Plotkin}, Eds. MIT Press, 166--177.
626: 
627: \bibitem[\protect\citeauthoryear{Bruynooghe and Pereira}{Bruynooghe and
628:   Pereira}{1984}]{BP84}
629: {\sc Bruynooghe, M.} {\sc and} {\sc Pereira, L.-M.} 1984.
630: \newblock {D}eduction revision by intelligent backtracking.
631: \newblock In {\em Implementation of Prolog}, {J.~Campbell}, Ed. Ellis Horwood,
632:   194--215.
633: 
634: \bibitem[\protect\citeauthoryear{Bruynooghe, Vandecasteele, de~Waal, and
635:   Denecker}{Bruynooghe et~al\mbox{.}}{1998}]{BVWD98}
636: {\sc Bruynooghe, M.}, {\sc Vandecasteele, H.}, {\sc de~Waal, D.~A.}, {\sc and}
637:   {\sc Denecker, M.} 1998.
638: \newblock {D}etecting unsolvable queries for definite logic programs.
639: \newblock In {\em Principles of Declarative Programming, Proc.\ PLILP'98 and
640:   ALP'98}, {C.~Palamidessi}, {H.~Glaser}, {and} {K.~Meinke}, Eds. LNCS.
641:   Springer, 118--133.
642: 
643: \bibitem[\protect\citeauthoryear{Bruynooghe, Vandecasteele, de~Waal, and
644:   Denecker}{Bruynooghe et~al\mbox{.}}{1999}]{BVWD99}
645: {\sc Bruynooghe, M.}, {\sc Vandecasteele, H.}, {\sc de~Waal, D.~A.}, {\sc and}
646:   {\sc Denecker, M.} 1999.
647: \newblock {D}etecting unsolvable queries for definite logic programs.
648: \newblock {\em J. Functional and Logic Programming\/}~{\em 1999}, 1--35.
649: 
650: \bibitem[\protect\citeauthoryear{Dechter and Frost}{Dechter and
651:   Frost}{2002}]{Dechter02}
652: {\sc Dechter, R.} {\sc and} {\sc Frost, D.} 2002.
653: \newblock Backjump-based backtracking for constraint satisfaction problems.
654: \newblock {\em Artificial Intelligence\/}~{\em 136,\/}~2, 147--188.
655: 
656: \bibitem[\protect\citeauthoryear{Ginsberg}{Ginsberg}{1993}]{ginsberg93}
657: {\sc Ginsberg, M.~L.} 1993.
658: \newblock Dynamic backtracking.
659: \newblock {\em Journal of Artificial Intelligence Research\/}~{\em 1}, 25--46.
660: 
661: \bibitem[\protect\citeauthoryear{Robinson}{Robinson}{1965}]{Rob65}
662: {\sc Robinson, J.~A.} 1965.
663: \newblock Automated deduction with hyper-resolution.
664: \newblock {\em Int. J. Computh. Math\/}~{\em 1}, 227--234.
665: 
666: \bibitem[\protect\citeauthoryear{Rosiers and Bruynooghe}{Rosiers and
667:   Bruynooghe}{1987}]{RB86}
668: {\sc Rosiers, W.} {\sc and} {\sc Bruynooghe, M.} 1987.
669: \newblock {E}mpirical study of some constraint satisfaction algorithms.
670: \newblock In {\em Artificial Intelligence II, Methodology, Systems,
671:   Applications, Proc. AIMSA'86}, {P.~Jorrand} {and} {V.~Sgurev}, Eds. North
672:   Holland, 173--180.
673: 
674: \bibitem[\protect\citeauthoryear{Van~Hentenryck}{Van~Hentenryck}{1989}]{Hentbo%
675: ok}
676: {\sc Van~Hentenryck, P.} 1989.
677: \newblock {\em Constraint Satisfaction in Logic Programming}.
678: \newblock MIT Press.
679: 
680: \end{thebibliography}
681: 
682: 
683: \label{lastpage}
684: 
685: 
686: 
687: 
688: \end{document}
689: