prologeclipse-clp

element/3 on matrix to find index returns range when value can only be at a single index


I have a matrix Board which starts as:

[](
    [](1,2,3,4,5,6),
    [](_,1,_,_,5,_),
    [](_,5,1,_,_,6),
)

The goal of the program is solve it in a Sudoku style, where _ are unknowns.

The first rule I apply is that each row can contain no duplicate digits:

puzzle(Board) :-
    dim(Board, [M,N]), % the board has M rows and N columns
    Board :: 1..N, % every value in the board must be between 1 and N

    ( for(I,1,M), param(Board) do
        alldifferent(Board[I,*]) % no row contains duplicate numbers
    ),

    ( for(I,2,M), param(Distance, Board, N) do
        ( for(J,1,N), param(Distance, Board, I) do
            element(J, Board[I,*], Value),
            element(IndexAbove, Board[I-1,*], Value),
            abs(IndexAbove - J) #=< 2
        )
    ).

From printing Board after the alldifferent loop, I can see that it has the state:

[](
    [](1, 2, 3, 4, 5, 6),
    [](_1536{[2 .. 4, 6]}, 1, _1550{[2 .. 4, 6]}, _1564{[2 .. 4, 6]}, 5, _1578{[2 .. 4, 6]}),
    [](_1606{[2 .. 4]}, 5, 1, _1620{[2 .. 4]}, _1634{[2 .. 4]}, 6)
)

which makes sense, as no value in the second row can be 1 or 5, but all other digits would still be valid (and similarly with the third row). The next loop starts with the second row, and for each value in the row, finds the index of that same value in the row above and asserts that the difference in index is not greater than 2.

My issue is that when I print the value of IndexAbove, I get _27569{4 .. 6} (when I=3,J=2) even though I can confirm that Value=5 and so my index should be 2 rather than a range of potential values (as none of the values at indices 4..6 have 5 in their domain). How can I make IndexAbove unify as 2?

P.S. I don't really understand the matrix notation and when it can be used (Board[I,*] appears to get the Ith row, but I can't use this to print the row for example). I am basing the work off of https://eclipseclp.org/examples/sudoku.ecl.txt


Solution

  • As far as I can tell, your code works correctly:

    ?- Board = []( [](1,2,3,4,5,6), [](_,1,_,_,5,_), [](_,5,1,_,_,6) ),
       puzzle(Board),
       labeling(Board).
    No (0.00s cpu)
    

    The example fails as expected, because the positions of the 5 violate your distance-constraints. If you relax the constraints, or change the positions, solutions are correctly found.

    The observations you have made concern intermediate states of computation, where consequences of domains and constraints have been partially propagated. While developing your model, you should not be too concerned about this. During execution, there are many individual propagation/inference steps, and trying to follow the exact evolution of the domains quickly gets out of hand. The only thing that really matters (and is guaranteed) is that the constraints are satisfied when all variables have been instantiated, i.e. when labeling has finished.

    The implementations of individual constraints (such as element/3) have multiple degrees of freedom regarding strength of inference and algorithmic complexity. The reason for the behaviour in this concrete example is that (this implementation of) the element/3 constraint only maintains bounds-consistency with respect to the array variables: the fact that the domains have holes is ignored. This reduces computational complexity during propagation steps, but means that failures and domain reductions may be detected later. However, these details only affect performance, not correctness.