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: