For the QuasiGroup completion problem I implemented two models. One of them is a model based on nothing but channeling constraints (based on a study by Dotu). The other one is a model based on the fact that every value needs to occur in ever row/column. Here's a little script :
flag :- fail.
:- lib(ic).
:- import occurrences/3 from ic_global.
test :-
O is 9, % Order of the puzzle
dim(Variables, [O,O]), Variables :: 1..O,
6 is Variables[1,1], 8 is Variables[6,2],
dim(Dual1, [O,O]), Dual1 :: 1..O,
dim(Dual2, [O,O]), Dual2 :: 1..O,
(flag ->
(multifor([I,V], 1, O), param(Variables, O) do
ic_global:occurrences(V, Variables[I,1..O], 1),
ic_global:occurrences(V, Variables[1..O,I], 1)
)
;
(multifor([I,J,K], 1, O), param(Variables, Dual1, Dual2) do
#=(Variables[I,J], K, Bool),
#=(Dual1[I,K], J, Bool),
#=(Dual2[J,K], I, Bool)
)
),
search(Variables, 0, input_order, indomain, complete, [backtrack(Backtracks)]),
write(Variables), nl,
write('Backtracks : '), write(Backtracks), nl.
I tried it out on a bunch of benchmarks (more than 10 puzzles). The total number of backtracks is larger than 500 but what struck me was that the number is the same in both models. The number of backtracks for each problem in the set is the same, too.
The little script above also reports the same number of backtracks. I'm curious why this happens. What does ic_global:occurrences/
do that makes it behave so similarly (though it is a little slower)?
The occurrences/3 constraint achieves arc-consistency, i.e. it eagerly removes, from the domains of its argument variables, all values that do not occur in any solution to the constraint.
If you can establish arc-consistency for your whole problem, then any search procedure will find solutions with the absolute minimum number of backtracks: first solution with 0 backtracks, second solution after 1 backtrack, Nth after N-1 backtracks. This isn't often achieved, because even when you model the problem with a conjunction of constraints that all achieve arc-consistency individually, that doesn't mean you have arc-consistency over the problem as a whole.
One major reason for the existence of these global constraints is that they can usually achieve higher levels of consistency than a conjunction of many small constraints. In this light, it is remarkable that your "dual" formulation seems to behave identical to the occurrences-based one.
I have expanded your program a bit, and looked at a number of alternative formulations that can be easily written using the available global constraints:
:- lib(ic).
:- lib(ic_global).
:- lib(ic_global_gac).
test(Model) :-
O is 9, % Order of the puzzle
dim(Variables, [O,O]), Variables :: 1..O,
6 is Variables[1,1], 8 is Variables[6,2],
(Model=occ ->
(multifor([I,V], 1, O), param(Variables, O) do
ic_global:occurrences(V, Variables[I,1..O], 1),
ic_global:occurrences(V, Variables[1..O,I], 1)
)
;Model=gcc ->
(for(V, 1, O), foreach(gcc(1,1,V),Bounds) do true ),
(for(I, 1, O), param(Variables, O, Bounds) do
ic_global_gac:gcc(Bounds, Variables[1..O,I]),
ic_global_gac:gcc(Bounds, Variables[I,1..O])
)
;Model=gcm ->
(for(V, 1, O), foreach(gcc(1,1,V),Bounds) do true ),
(for(_, 1, O), foreach(Bounds,RowColBounds), param(Bounds) do true ),
ic_global_gac:gcc_matrix(RowColBounds, RowColBounds, Variables)
;Model=ad(Strength) ->
(for(I, 1, O), param(Variables,O,Strength) do
Strength:alldifferent(Variables[1..O,I]),
Strength:alldifferent(Variables[I,1..O])
)
;Model=adm ->
ic_global:alldifferent_matrix(Variables)
;Model=dual ->
dim(Dual1, [O,O]), Dual1 :: 1..O,
dim(Dual2, [O,O]), Dual2 :: 1..O,
(multifor([I,J,K], 1, O), param(Variables, Dual1, Dual2) do
#=(Variables[I,J], K, Bool),
#=(Dual1[I,K], J, Bool),
#=(Dual2[J,K], I, Bool)
)
),
search(Variables, 0, input_order, indomain, complete, [backtrack(Backtracks)]),
( foreacharg(Row,Variables) do writeln(Row) ),
write('Backtracks : '), write(Backtracks), nl.
With your small test instance, these behave as follows:
Goal #backtracks until first solution
test(occ) 3
test(gcc) 0
test(gcm) 0
test(ad(ic)) 29
test(ad(ic_global)) 0
test(ad(ic_global_gac)) 0
test(adm) 0
test(dual) 3
With larger problem instances you might find more interesting differences. However, the adm
and gcm
models (where the whole problem is represented with a single constraint) should always exhibit the minimal backtracking behaviour.