Other forms of choices are possible. If X2 is a variable that can take integer values from 0 to 3 (assume it has been declared as( X1=0 ; X1=1 )
X2::0..3
),
we can make a n-ary search tree node by writing
or more compactly( X2=0 ; X2=1 ; X2=2 ; X2=3 )
However, choices do not necessarily involve choosing a concrete value for a variable. It is also possible to make disjoint choices by domain splitting, e.g.indomain(X2)
or by choosing a value in one branch and excluding it in the other:( X2 #=< 1 ; X2 #>= 2 )
In the following examples, we will mainly use simple labeling, which means that the search tree nodes correspond to a variable and a node's branches correspond to the different values that the variable can take.( X2 = 0 ; X2 #>= 1 )
Figure 12.5 shows how variable selection reshapes a search tree. If we decide to choose values for X1 first (at the root of the search tree) and values for X2 second, then the search tree has one particular shape. If we now assume a depth-first, left-to-right traversal by backtracking, this corresponds to one particular order of visiting the leaves of the tree: (0,0), (0,1), (0,2), (0,3), (1,0), (1,1), (1,2), (1,3).![]()
Figure 12.5: The effect of variable selection
By combining variable and value selection alone, a large number of different heuristics can be implemented. To give an idea of the numbers involved, table 12.7 shows the search space sizes, the number of possible search space traversal orderings, and the number of orderings that can be obtained by variable and value selection (assuming domain size 2).![]()
Figure 12.6: The effect of value selection
Variables Search space Visiting orders Selection Strategies 1 2 2 2 2 4 24 16 3 8 40320 336 4 16 2.1*1013 1.8*107 5 32 2.6*1035 3.5*1015 n 2n 2n! 22n−1 Πi=0n−1 (n−1)2i
Figure 12.7: Flexibility of Variable/Value Selection Strategies
:- lib(ic). queens(N, Board) :- length(Board, N), Board :: 1..N, ( fromto(Board, [Q1|Cols], Cols, []) do ( foreach(Q2, Cols), count(Dist,1,_), param(Q1) do noattack(Q1, Q2, Dist) ) ). noattack(Q1,Q2,Dist) :- Q2 # |
We start naively, using the pre-defined labeling-predicate that comes with the ic library. It is defined as follows:?- queens(16, Vars), % model labeling(Vars). % search
labeling(AllVars) :- ( foreach(Var, AllVars) do indomain(Var) % select value ). |
:- lib(ic_search). labeling_b(AllVars) :- ( fromto(AllVars, Vars, VarsRem, []) do delete(Var, Vars, VarsRem, 0, first_fail), % dynamic var-select indomain(Var) % select value ). |
labeling_c(AllVars) :- middle_first(AllVars, AllVarsPreOrdered), % static var-select ( foreach(Var, AllVarsPreOrdered) do indomain(Var) % select value ). |
:- lib(lists). middle_first(List, Ordered) :- halve(List, Front, Back), reverse(Front, RevFront), splice(Back, RevFront, Ordered). |
labeling_d(AllVars) :- middle_first(AllVars, AllVarsPreOrdered), % static var-select ( fromto(AllVarsPreOrdered, Vars, VarsRem, []) do delete(Var, Vars, VarsRem, 0, first_fail), % dynamic var-select indomain(Var) % select value ). |
labeling_e(AllVars) :- middle_first(AllVars, AllVarsPreOrdered), % static var-select ( fromto(AllVarsPreOrdered, Vars, VarsRem, []) do delete(Var, Vars, VarsRem, 0, first_fail), % dynamic var-select indomain(Var, middle) % select value ). |
N = 8 12 14 16 32 64 128 256 labeling_a 10 15 103 542 labeling_b 10 16 11 3 4 148 labeling_c 0 3 22 17 labeling_d 0 0 1 0 1 1 labeling_e 3 3 38 3 7 1 0 0
Figure 12.8: N-Queens with different labeling strategies: Number of backtracks
labeling(AllVars) :- init_backtracks, ( foreach(Var, AllVars) do count_backtracks, % insert this before choice! indomain(Var) ), get_backtracks(B), printf("Solution found after %d backtracks%n", [B]). |
:- local variable(backtracks), variable(deep_fail). init_backtracks :- setval(backtracks,0). get_backtracks(B) :- getval(backtracks,B). count_backtracks :- setval(deep_fail,false). count_backtracks :- getval(deep_fail,false), % may fail setval(deep_fail,true), incval(backtracks), fail. |