3
votes
test(X, Y) :- 
    X ins 1..3,
    Y ins 1..3,
    X #\= Y.

Here is my attempt at doing it. The goal would be to type this into SWI-Prolog so that this output comes out.

?- test(X, Y).

X = 1

Y = 2 ;

X = 2,

Y = 1;

X = 3,

Y = 1 ; 

... etc.

I'm actually trying to solve the 8-queens problem using prolog and have this so far.

eight_queens(Qs, L) :-
    Qs = [ [X1,Y1], [X2, Y2], [X3, Y3], [X4, Y4], [X5, Y5], [X6, Y6], [X7, Y7], [X8, Y8], [X9, Y9] ],
    Qs ins 1..9,
    X1 #\= X2,
    X1 #\= X3,
    ...
    etc.

But I keep getting this error: "Arguments are not sufficiently instantiated" for both the test function and the eight_queens problem.

2

2 Answers

3
votes

ins is used for lists, in is used for single variable so in your example:

test(X, Y) :- 
    X ins 1..3,
    Y ins 1..3,
    X #\= Y.

X,Y are assumed to be lists. This does not produces a syntax error, but produces error when trying to run it with X,Y not being lists.

Also when using in Low..High doesn't mean that the variable is int just X=<High and X>=Low. In order to put the constraint to be integers use label/1:

:- use_module(library(clpfd)).

%using in/
    test(X,Y):- X in 1..3,Y in 1..3,label([X,Y]), X#\=Y.

%2nd way using ins
    test(X,Y):- [X,Y] ins 1..3, label([X,Y]), X#\=Y.

Example:

?- test(X,Y).
X = 1,
Y = 2 ;
X = 1,
Y = 3 ;
X = 2,
Y = 1 ;
X = 2,
Y = 3 ;
X = 3,
Y = 1 ;
X = 3,
Y = 2 ;
false.
3
votes

Besides the observation about in/2 and ins/2 posted by @coder, that solve your imminent problem, I would add the following points that are good to keep in mind when using CLP(FD):

1. Always make labeling the last goal

First let's observe the answers for the variant marked as 2nd way using ins in @coder's post but without the goal label/1:

test(X, Y) :- 
    [X,Y] ins 1..3,
    X #\= Y.

?- test(X,Y).
X in 1..3,          % residual goal
X#\=Y,              % residual goal
Y in 1..3.          % residual goal

Since there is no unique answer to the query, Prolog answers with residual goals (see section A.8.8 of the CLP(FD) manual) for more information). These residual goals are constraints that are being propagated and with every additional (non-redundant) constraint the domain is narrowed. If this does not lead to a unique solution like in the example above you can get concrete values by labeling the constrained variables (e.g. with label/1). This observation suggests to use labeling as the last goal:

?- test(X,Y), label([X,Y]).
X = 1,
Y = 2 ;
X = 1,
Y = 3 ;
X = 2,
Y = 1 ;
X = 2,
Y = 3 ;
X = 3,
Y = 1 ;
X = 3,
Y = 2.

This is obviously the same result as with @coders version but the three pairs (X,Y) = (1,1) ∨ (2,2) ∨ (3,3) are not considered when labeling due to the constraint X#\=Y being posted before the goal label([X,Y]). In @coder's version it is the other way around: label([X,Y]) is delivering all three pairs as possible solutions and the last goal X#\=Y is eliminating them subsequently. To see this just leave the last goal as a comment and query the predicate:

test(X,Y):- [X,Y] ins 1..3, label([X,Y]). %, X#\=Y.

?- test(X,Y).
X = Y, Y = 1 ;      % <- (1,1)
X = 1,
Y = 2 ;
X = 1,
Y = 3 ;
X = 2,
Y = 1 ;
X = Y, Y = 2 ;      % <- (2,2)
X = 2,
Y = 3 ;
X = 3,
Y = 1 ;
X = 3,
Y = 2 ;
X = Y, Y = 3.       % <- (3,3)

The difference is minuscule in this example, so there's nothing wrong with @coder's version. But in general this might lead to a big difference if the constraints posted after labeling exclude a lot of candidates. So it's good practice to always put labeling as the last goal.

2. Separate labeling from the actual relation

Coming from the previous observations it is opportune to divide the predicate into a core relation that is posting all the constraints and labeling. Consider the restructured predicate test/2 as a template:

test(X,Y) :-
   test_(X,Y,L),     % the core relation
   label(L).         % labeling

test_(X,Y,L) :-
   L=[X,Y],          % variables to be labeled in a flat list
   L ins 1..3,
   X#\=Y.

The predicate test_/3 is describing the actual relation by posting all the necessary constraints and has a list as an additional argument that contains all the variables to be labeled. Obtaining the latter might not be trivial, depending on the data structures your arguments come with (consider for example a list of lists as an argument that you want to turn into a flat list for labeling). So the predicate test/2 is only calling test_/3 and subsequently the labeling goal. This way you have a clean and easily readable separation.

3. Try different labeling strategies

The goal label(L) is the simplest way to do labeling. It is equivalent to labeling([],L). The first argument of labeling/2 is a list of options that gives you some control over the search process, e.g. labeling([ff],L) labels the leftmost variable with the smallest domain next, in order to detect infeasibility early. Depending on the problem you are trying to solve different labeling strategies can lead to results faster or slower. See the documentation of labeling/2 for available labeling strategies and further examples.