In my Prolog course the past semester, I fell a little behind around the time CLP was introduced. Now I'm trying to catch up, and have tried my hand at a past exam that the professor supplied to all students.
In particular, there was this question:
What is the domain of the decision variable Z in CLP(FD) after the following query:
?- X in 1..7, Y in -3..100, Y #> X, Z #\= 0, Z #= Y - X.
It seems to me that the answer should be
Z in 1..99
but when I ran it in my SWI-Prolog installation to double-check, I got
Z in -5.. -1\/1..99
which seems to be based on a naive comparison of the maximum and minimum values of X & Y, without regard for the constraint linking them (Y #> X).
I realize that concessions to feasibility have to be made here and the domains returned will sometimes be less restrictive than they could be, but I'm surprised to see it fail on such a simple example.
My questions
I assume that this has to do with how CLP chooses to propagate (or not to propagate) various constraints internally, but I don't understand how it does that - it's all something of a black box for me. How, exactly (or failing that, approximately), does CLP propagate its constraints?
Is there any way to make CLP(FD) apply the constraint appropriately, perhaps by reordering? I've already tried tacking on an extra Y #> X at the end, but that didn't change any of the variables domains.
It seems to me that the answer should be
Z in 1..99
How can you be so sure that you are right? This is one of the nice properties of constraints: You can verify this most easily:
?- X in 1..7, Y in -3..100, Y #> X, Z #\= 0, Z #= Y -X.
X in 1..7,
Z+X#=Y,
X#=<Y+ -1,
Z in -5.. -1\/1..99,
Y in 2..100.
?- X in 1..7, Y in -3..100, Y #> X, Z #\= 0, Z #= Y -X, Z #< 0.
false.
OK, now I believe what you said.
So you have discovered here an inconsistency which is present also in SICStus' native library(clpfd) as well as library(clpz). First please note that the answer given was not incorrect! It said: Yes, there are solutions provided X in 1..7, Z+X#=Y, X#=<Y+ -1, Z in -5.. -1\/1..99, Y in 2..100. is true. Helas, this is not true.
So that answer is a bit like the legalese in many insurance contracts where they say, yes we will pay, provided all that tiny unreadable print holds, but in reality you could replace that wall of microtext by a big fat false.
In general, such inconsistencies are inevitable since CLP(FD)/CLP(Z) as defined in above systems permits to formulate undecidable problems. Thus, no matter how evolved your constraint solver is, we have the guarantee that there will be always cases that we cannot solve. That's a scientific, mathematical law, much more reliable than empirical laws like gravity or that speed limit.
The inconsistency here is effectively an engineering tradeoff. As long as nobody complains and doesn't have a convincing use case, the developers of such systems will not see a reason to improve. After all, such an improvement might slow down existing use cases.
How, exactly (or failing that, approximately), does CLP propagate its constraints?
Actually, for any problem of realistic size, nobody knows. But this is not necessary either. In the case of CLP(FD), the fundamental element are the domains attached to the logical variables. You see them as (in)/2 goals like Z in -5.. -1\/1..99. Connected between them are the actual constraints. In your case Y #> X and Z #= Y-X. These constraints now only see the domains of the variables and try to maintain consistency between them. As an even coarser approximation, the domains are seen as intervals thus Z in -5 .. 99 instead of above. What (most of them) do not see are the other constraints. In this case, there is no direct connection between Y #> X and Z #= Y-X. And thus the inconsistency. Such limited consistency checks are much easier to implement and also quite fast and often outperform more complete algorithms. With the discovery of better algorithms things evolve. A nice example is all_distinct/1 which maintains consistency between all variables using Regin's algorithm, whereas all_different/1 only maintains consistency between each pair of variables. But in any case: these things evolve and it is a bit of a surprise that this is an exam question.
Is there any way to make CLP(FD) apply the constraint appropriately ...?
?- X in 1..7, Y in -3..100, Y #> X, Z #\= 0, Z #= Y -X, clpfd:contracting([X,Y,Z]).
X in 1..7,
Z+X#=Y,
X#=<Y+ -1,
Z in 1..99,
Y in 2..100.
But most will ignore this issue and just add labeling([],[X,Y])
What is the domain of Z?
That is an ambiguous question. Give both as an answer.
I have done very little programming in Prolog and find it quite difficult so far.
I was given the question: A gorilla moves along an 8x8 grid and can only move right or up. it has to remain within the grid and must finish at (8,8) starting at any arbitrary location.
Write a move predicate that describes all the possible moves.
My attempt:
move(X,Y,X+1,Y).
move(X,Y,X,Y+1).
Write a path predicate that uses the move predicate to determine the path thte robot shuld take.
My attempt:
path('right'):-
move(X,Y,X+1,Y).
path('up'):-
move(X,Y,X,Y+1).
Write prolog predicates that model blockages at (1,2), (4,2), and (4,1).
So far, from what I have found it seems I need to set up a list that would give all possible positions.
I have written a list of the possible positions but do not understand how to implement it:
[(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),
(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),(2,7),(2,8),
(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),
(4,1),(4,2),(4,3),(4,4),(4,5),(4,6),(4,7),(4,8),
(5,1),(5,2),(5,3),(5,4),(5,5),(5,6),(5,7),(5,8),
(6,1),(6,2),(6,3),(6,4),(6,5),(6,6),(6,7),(6,8),
(7,1),(7,2),(7,3),(7,4),(7,5),(7,6),(7,7),(7,8),
(8,1),(8,2),(8,3),(8,4),(8,5),(8,6),(8,7),(8,8)]
This seems like it would be a simple program but I cannot seem to grasp the concepts or at least put them all together into a workable program.
Any help in direction would be greatly appreciated.
There are quite some issues with your code. Let's go through it one at a time.
1. Possible positions
Although your list of possible positions is OK, I wouldn't hard-code it like that. It's very easy to do a check if a position is on the grid:
grid_position(X, Y) :-
X >= 1,
X =< 8,
Y >= 1,
Y =< 8.
Do note that this can only be used to verify a given position. If you want to be able to generate all possible positions, you can use in/2 from library(clpfd).
2. Allowed positions
If there is no simple logic as above for positions that are blocked, there is no other way than to enumerate them yourself.
blocked(1, 2).
blocked(4, 2).
blocked(4, 1).
Using this, we can determine which are the allowed positions for our gorilla: any position that is on the grid, but is not blocked.
allowed_position(X, Y) :-
grid_position(X, Y),
\+blocked(X, Y).
3. move
The main problem here is that writing X+1 in the head of the clause doesn't do what you think it does. To evaluate arithmetic expressions, you need to use the is predicate.
Additionally, I would only allow a move if the next location is allowed. Since the gorilla is already at the current location, I don't include a check to see if this location is actually allowed.
move(X, Y, X2, Y) :-
X2 is X + 1,
allowed_position(X2, Y).
move(X, Y, X, Y2) :-
Y2 is Y + 1,
allowed_position(X, Y2).
4. path
Here's how I interpret the requirement: given a start position, return the list of moves used to reach the end position.
To do this, we're going to need 3 arguments: the X and Y positions, and the output. The output here will be a list of positions rather than a list of moves, I'll leave it up to you to change that if needed.
So what makes up our path? Well, first you make one move, and then you find the rest of the path from the next position.
path(X, Y, [(X,Y)|Ps]) :-
move(X, Y, X2, Y2),
path(X1, Y1, Ps).
Of course we have to make sure this ends at the target position, so for base case we can use:
path(8, 8, (8, 8)).
You may also want to verify that the initial position is an allowed position, which I have left out.
Combine everything, and you get output such as below.
?- path(5,6,L).
L = [(5,6),(6,6),(7,6),(8,6),(8,7)|(8,8)] ? ;
L = [(5,6),(6,6),(7,6),(7,7),(8,7)|(8,8)] ? ;
L = [(5,6),(6,6),(7,6),(7,7),(7,8)|(8,8)] ? ;
...
This may not be exactly what you're looking for, but I hope it helps you well on the way.
So you might want to say where you are moving while you're doing that. So I'll suggest a predicate move/3 like this:
% move(From_Position, To_Position, Direction).
move((X,Y),(X,Y1), up) :-
grid(G),
member((X,Y1),G),
Y1 is Y + 1.
move((X,Y),(X1,Y), rigth):-
grid(G),
member((X1,Y),G),
X1 is X + 1.
The grid calls are there to ensure that you'll always stay on the grid. you also could use a smarter predicate in_grid and avoid the member call (which is quite time consuming).
grid([(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),
(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),(2,7),(2,8),
(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),
(4,1),(4,2),(4,3),(4,4),(4,5),(4,6),(4,7),(4,8),
(5,1),(5,2),(5,3),(5,4),(5,5),(5,6),(5,7),(5,8),
(6,1),(6,2),(6,3),(6,4),(6,5),(6,6),(6,7),(6,8),
(7,1),(7,2),(7,3),(7,4),(7,5),(7,6),(7,7),(7,8),
(8,1),(8,2),(8,3),(8,4),(8,5),(8,6),(8,7),(8,8)]).
A path should probably be a list of directions:
path((8,8), []).
path(Position, [Direction| Before]):-
\+ Position = (8,8),
move(Position, NewPosition, Direction),
path(NewPosition,Before).
To Accumulate, you can use bagof or setof
all_paths(Position,Paths):-
setof(Path,path(Position,Path),Paths).
Sorry by the first ask, I'm new at the hood... I did a cleaning on the code. The problem is:
I have a square grid with paths and obstacles. I want to find the shortest path from a point to another. This is part of an artificial intelligence. When the path is too large, I can not see the whole list of points on the bash, but in the game, the character who travels this path, it does not at shortest path. So, my question is, how I can change this code to solve the shortest path. Thank you so much!
mov(X1,Y1,X2,Y2):-
pos(X1,Y1), X2 is X1 , Y2 is Y1+1 ,pos(X2,Y2).
mov(X1,Y1,X2,Y2):-
pos(X1,Y1), X2 is X1 , Y2 is Y1-1 ,pos(X2,Y2).
mov(X1,Y1,X2,Y2):-
pos(X1,Y1), X2 is X1+1 , Y2 is Y1 , pos(X2,Y2).
mov(X1,Y1,X2,Y2):-
pos(X1,Y1), X2 is X1 -1 , Y2 is Y1 , pos(X2,Y2).
path(X1,Y1,X2,Y2,Path) :-
travel(pos(X1,Y1),pos(X2,Y2),[pos(X1,Y1)],Q),
reverse(Q,Path).
travel(pos(X1,Y1),pos(X2,Y2),P,[pos(X2,Y2)|P]) :-
mov(X1,Y1,X2,Y2).
travel(pos(X1,Y1),pos(X2,Y2),Visited,Path) :-
mov(X1,Y1,X,Y),
pos(X,Y) \== pos(X2,Y2),
\+member(pos(X,Y),Visited),
travel(pos(X,Y),pos(X2,Y2),[pos(X,Y)|Visited],Path).
First some Prolog advice.
member/2 is a built-in, you should not have to define it.
ISO negation is \+, not not/1.
For performance, memberchk/2 beats member/2.
I see a lot of foo(X,Y) :- X == Y, ... in your code. It's much better if you just say foo(X,X) and save yourself the trouble of making explicit tests like this, unless you're going to do a conditional expression to avoid a choice point or something.
Lots of cuts in this code. Cuts and bugs tend to be great friends because the cut can undermine reasonable-looking code by preventing it from being executed.
If I had to solve this problem, I would want to separate the shortest-path logic from the grid traversal logic. You'll never be able to debug this, and even if you do, what you'll have is a one of those unreadable blocks of code that cannot be modified. It's clear that you have an explosion of terms because you're embedding the traversal logic in the path finding logic. Break them out into two separate steps and you will probably find that you get smaller pieces you can meaningfully test and debug. This is a good way of life with programming, regardless of the language: what would you do if you needed to change the grid structure or make the pathfinding more intelligent or complex? Keeping pieces granular always helps for managing change.
As for S.O. etiquette, this isn't great: you should talk about what doesn't work and what you've tried and you want to supply a minimum, complete, verifiable example. I suspect in producing such a thing you'd probably solve the problem yourself.
Here my problem is I want to check whether two nodes are connected or not.
My Knowledge base is,
edge(a,b):-!.
edge(b,a):-!.
edge(a,e):-!.
edge(e,a):-!.
edge(b,c):-!.
edge(c,b):-!.
edge(b,d):-!.
edge(d,b):-!.
edge(c,e):-!.
edge(e,c):-!.
edge(d,e):-!.
edge(e,d):-!.
edge(a,f):-!.
edge(f,a):-!.
isConnected(X,X):-!.
isConnected(X,Y):-edge(X,Y),!.
isConnected(X,Z):-not(edge(X,Y)),edge(X,Y),isConnected(Y,Z),!.
isConnected(X,Z):not(edge(X,Y)),edge(X,Z),not(isConnected(Y,Z)),isConnected(Z,Y),!.
Whoa there. That's a lot of cuts. Cuts are sometimes helpful in Prolog for pruning unnecessary answers, but when you have a fact like this:
edge(a, b).
There is absolutely nothing to be gained by writing it as:
edge(a, b) :- !.
After all, there's no choice point there, because there are no variables there and no alternate solutions. So first, let's fix your facts by removing all those cuts.
Next let's look at what isConnected is saying. Let's read it aloud in English and see if it makes sense.
X is connected to X.
X is connected to Y if there is an edge from X to Y.
X is connected to Z if there is not an edge from X to Y, but there is an edge from X to Y and Y is connected to Z. (???)
X is connected to Z if there is not an edge from X to Y, but there is an edge from X to Z, and Y is not connected to Z but Z is connected to Y. (???)
The first two seem quite reasonable. The third one seems to contradict itself right away. How can not(edge(X, Y)) be true at the same time as edge(X, Y)? Keep in mind that the comma in Prolog means and, not just then. The rest of the clause is meaningless because these two conditions cannot ever both be true. Probably what you meant to say was something like this: X is connected to Z if there is an edge from X to Y and Y is connected to Z. That would look like this in Prolog:
isConnected(X, Z) :- edge(X, Y), isConnected(Y, Z).
Logically, this is certainly true, but for any kind of complex graph this is going to be monstrously expensive to calculate, because checking if X is connected to Z might imply checking if Y is connected to Z for all the same nodes.
Your fourth clause has a typo in that the : should be a :-. More importantly, it looks like you're trying to compensate here for the directionality of your edges. A better place to do this would have been around step 2, providing both cases:
isConnected(X, Y) :- edge(X, Y) ; edge(Y, X).
I'm not sure, based on your fact database, whether you actually mean this though; you've duplicated all your facts to account for both directions. If the fact database represents a directed graph, this is probably necessary and the rules should not try inverting the nodes. If it instead represents an undirected graph, your predicate should just account for it with a rule like this that checks both sides, and you should only list each edge once. Doing it both ways, you're telling Prolog to do a bunch of unnecessary work, as it first checks edge(a, b), then the rule inverts it to edge(b, a), then moves on to the next fact edge(b, a) and then the rule inverts it to edge(a, b), in effect checking everything twice.
The elephant in the room here is that even if you do successfully turn this into a logical solution to the problem it's going to be hideously inefficient. There are algorithms for determining if two things are connected that keep track of what has been seen and what has not, and I think you'd be much better off implementing one of those.