What are the optimal green cuts for successor arithmetics sum? - prolog

To grok green cuts in Prolog I am trying to add them to the standard definition of sum in successor arithmetics (see predicate plus in What's the SLD tree for this query?). The idea is to "clean up" the output as much as possible by eliminating all useless backtracks (i.e., no ... ; false) while keeping identical behavior under all possible combinations of argument instantiations - all instantiated, one/two/three completely uninstantiated, and all variations including partially instantiated args.
This is what I was able to do while trying to come as close as possible to this ideal (I acknowledge false's answer to how to insert green cuts into append/3 as a source):
natural_number(0).
natural_number(s(X)) :- natural_number(X).
plus(X, Y, X) :- (Y == 0 -> ! ; Y = 0), (X == 0 -> ! ; true), natural_number(X).
plus(X, s(Y), s(Z)) :- plus(X, Y, Z).
Under SWI this seems to work fine for all queries but those with shape ?- plus(+X, -Y, +Z)., as for SWI's notation of predicate description. For instance, ?- plus(s(s(0)), Y, s(s(s(0)))). yields Y = s(0) ; false.. My questions are:
How do we prove that the above cuts are (or are not) green?
Can we do better than the above program and eliminate also the last backtrack by adding some other green cuts?
If yes, how?

First a minor issue: the common definition of plus/3 has the first and second argument exchanged which allows to exploit first-argument indexing. See Program 3.3 of the Art of Prolog. That should also be changed in your previous post. I will call your exchanged definition plusp/3 and your optimized definition pluspo/3. Thus, given
plusp(X, 0, X) :- natural_number(X).
plusp(X, s(Y), s(Z)) :- plusp(X, Y, Z).
Detecting red cuts (question one)
How to prove or disprove red/green cuts? First of all, watch for "write"-unifications in the guard. That is, for any such unifications prior to the cut. In your optimized program:
pluspo(X, Y, X) :- (Y == 0 -> ! ; Y = 0), (X == 0 -> ! ; true), ...
I spot the following:
pluspo(X, Y, X) :- (...... -> ! ; ... ), ...
So, let us construct a counterexample: To make this cut cut in a red manner, the "write unification" must make its actual guard Y == 0 true. Which means that the goal to construct must contain the constant 0 somehow. There are only two possibilities to consider. The first or third argument. A zero in the last argument means that we have at most one solution, thus no possibility to cut away further solutions. So, the 0 has to be in the first argument! (The second argument must not be 0 right from the beginning, or the "write unification would not have a detrimental effect.). Here is one such counterexample:
?- pluspo(0, Y, Y).
which gives one correct solution Y = 0, but hides all the other ones! So here we have such an evil red cut!
And contrast it to the unoptimized program which gave infinitely many solutions:
Y = 0
; Y = s(0)
; Y = s(s(0))
; Y = s(s(s(0)))
; ... .
So, your program is incomplete, and any questions about further optimizing it are thus not relevant. But we can do better, let me restate the actual definition we want to optimize:
plus(0, X, X) :- natural_number(X).
plus(s(X), Y, s(Z)) :- plus(X, Y, Z).
In practically all Prolog systems, there is first-argument indexing, which makes the following query determinate:
?- plus(s(0),0,X).
X = s(0).
But many systems do not support (full) third argument indexing. Thus we get in SWI, YAP, SICStus:
?- plus(X, Y, 0).
X = Y, Y = 0
; false.
What you probably wanted to write is:
pluso(X, Y, Z) :-
% Part one: green cuts
( X == 0 -> ! % first-argument indexing
; Z == 0 -> ! % 3rd-argument indexing, e.g. Jekejeke, ECLiPSe
; true
),
% Part two: the original unifications
X = 0,
Y = Z,
natural_number(Z).
pluso(s(X), Y, s(Z)) :- pluso(X, Y, Z).
Note the differences to pluspo/3: There are now only tests prior to the cut! All unifications are thereafter.
?- pluso(X, Y, 0).
X = Y, Y = 0.
The optimizations so far relied only on investigating the heads of the two clauses. They did not take into account the recursive rule. As such, they can be incorporated into a Prolog compiler without any global analysis. In O'Keefe's terminology, these green cuts might be considered blue cuts. To cite The Craft of Prolog, 3.12:
Blue cuts are there to alert the Prolog system to a determinacy it should have noticed but wouldn't. Blue cuts do not change the visible behavior of the program: all they do is make it feasible.
Green cuts are there to prune away attempted proofs that would succeed or be irrelevant, or would be bound to fail, but you would not expect the Prolog system to be able to tell that.
However, the very point is that these cuts do need some guards to work properly.
Now, you considered another query:
?- pluso(X, s(s(0)), s(s(s(0)))).
X = s(0)
; false.
or to put a simpler case:
?- pluso(X, s(0), s(0)).
X = 0
; false.
Here, both heads apply, thus the system is not able to determine determinism. However, we know that there is no solution to a goal plus(X, s^n, s^m) with n > m. So by considering the model of plus/3 we can further avoid choicepoints. I'll be right back after this break:
Better use call_semidet/1!
It gets more and more complex and chances are that optimizations might easily introduce new errors in a program. Also optimized programs are a nightmare to maintain. For practical programming purposes use rather call_semidet/1. It is safe, and will produce a clean error should your assumptions turn out to be false.
Back to business: Here is a further optimization. If Y and Z are identical, the second clause cannot apply:
pluso2(X, Y, Z) :-
% Part one: green cuts
( X == 0 -> ! % first-argument indexing
; Z == 0 -> ! % 3rd-argument indexing, e.g. Jekejeke, ECLiPSe
; Y == Z, ground(Z) -> !
; true
),
% Part two: the original unifications
X = 0,
Y = Z,
natural_number(Z).
pluso2(s(X), Y, s(Z)) :- pluso2(X, Y, Z).
I (currently) believe that pluso2/3 is the optimal usage of green/blue cuts w.r.t. leftover choicepoints. You asked for a proof. Well, I think that is well beyond an SO answer...
The goal ground(Z) is necessary to ensure the non-termination properties. The goal plus(s(_), Z, Z) does not terminate, that property is preserved by ground(Z). Maybe you think it is a good idea to remove infinite failure branches too? In my experience, this is rather problematic. In particular, if those branches are removed automatically. While at first sight it seems to be a good idea, it makes program development much more brittle: An otherwise benign program change might now disable the optimization and thus "cause" non-termination. But anyway, here we go:
Beyond simple green cuts
pluso3(X, Y, Z) :-
% Part one: green cuts
( X == 0 -> ! % first-argument indexing
; Z == 0 -> ! % 3rd-argument indexing, e.g. Jekejeke, ECLiPSe
; Y == Z -> !
; var(Z), nonvar(Y), \+ unify_with_occurs_check(Z, Y) -> !, fail
; var(Z), nonvar(X), \+ unify_with_occurs_check(Z, X) -> !, fail
; true
),
% Part two: the original unifications
X = 0,
Y = Z,
natural_number(Z).
pluso3(s(X), Y, s(Z)) :- pluso3(X, Y, Z).
Can you find a case where pluso3/3 does not terminate while there are finitely many answers?

Related

How can Prolog derive nonsense results such as 3 < 2?

A paper I'm reading says the following:
Plaisted [3] showed that it is possible to write formally correct
PROLOG programs using first-order predicate-calculus semantics and yet
derive nonsense results such as 3 < 2.
It is referring to the fact that Prologs didn't use the occurs check back then (the 1980s).
Unfortunately, the paper it cites is behind a paywall. I'd still like to see an example such as this. Intuitively, it feels like the omission of the occurs check just expands the universe of structures to include circular ones (but this intuition must be wrong, according to the author).
I hope this example isn't
smaller(3, 2) :- X = f(X).
That would be disappointing.
Here is the example from the paper in modern syntax:
three_less_than_two :-
less_than(s(X), X).
less_than(X, s(X)).
Indeed we get:
?- three_less_than_two.
true.
Because:
?- less_than(s(X), X).
X = s(s(X)).
Specifically, this explains the choice of 3 and 2 in the query: Given X = s(s(X)) the value of s(X) is "three-ish" (it contains three occurrences of s if you don't unfold the inner X), while X itself is "two-ish".
Enabling the occurs check gets us back to logical behavior:
?- set_prolog_flag(occurs_check, true).
true.
?- three_less_than_two.
false.
?- less_than(s(X), X).
false.
So this is indeed along the lines of
arbitrary_statement :-
arbitrary_unification_without_occurs_check.
I believe this is the relevant part of the paper you can't see for yourself (no paywall restricted me from viewing it when using Google Scholar, you should try accessing this that way):
Ok, how does the given example work?
If I write it down:
sm(s(s(s(z))),s(s(z))) :- sm(s(X),X). % 3 < 2 :- s(X) < X
sm(X,s(X)). % forall X: X < s(X)
Query:
?- sm(s(s(s(z))),s(s(z)))
That's an infinite loop!
Turn it around
sm(X,s(X)). % forall X: X < s(X)
sm(s(s(s(z))),s(s(z))) :- sm(s(X),X). % 3 < 2 :- s(X) < X
?- sm(s(s(s(z))),s(s(z))).
true ;
true ;
true ;
true ;
true ;
true
The deep problem is that X should be Peano number. Once it's cyclic, one is no longer in Peano arithmetic. One has to add some \+cyclic_term(X) term in there. (maybe later, my mind is full now)

How to stop backtracking and end the recursion in Prolog

I'm currently learning SWI-Prolog. I want to implement a function factorable(X) which is true if X can be written as X = n*b.
This is what I've gotten so far:
isTeiler(X,Y) :- Y mod X =:= 0.
hatTeiler(X,X) :- fail,!.
hatTeiler(X,Y) :- isTeiler(Y,X), !; Z is Y+1, hatTeiler(X,Z),!.
factorable(X) :- hatTeiler(X,2).
My problem is now that I don't understand how to end the recursion with a fail without backtracking. I thought the cut would do the job but after hatTeilerfails when both arguments are equal it jumps right to isTeiler which is of course true if both arguments are equal. I also tried using \+ but without success.
It looks like you add cuts to end a recursion but this is usually done by making rule heads more specific or adding guards to a clause.
E.g. a rule:
x_y_sum(X,succ(Y,1),succ(Z,1)) :-
x_y_sum(X,Y,Z).
will never be matched by x_y_sum(X,0,Y). A recursion just ends in this case.
Alternatively, a guard will prevent the application of a rule for invalid cases.
hatTeiler(X,X) :- fail,!.
I assume this rule should prevent matching of the rule below with equal arguments. It is much easier just to add the inequality of X and Y as a conditon:
hatTeiler(X,Y) :-
Y>X,
isTeiler(Y,X),
!;
Z is Y+1,
hatTeiler(X,Z),
!.
Then hatTeiler(5,5) fails automatically. (*)
You also have a disjunction operator ; that is much better written as two clauses (i drop the cuts or not all possibilities will be explored):
hatTeiler(X,Y) :- % (1)
Y > X,
isTeiler(Y,X).
hatTeiler(X,Y) :- % (2)
Y > X,
Z is Y+1,
hatTeiler(X,Z).
Now we can read the rules declaratively:
(1) if Y is larger than X and X divides Y without remainder, hatTeiler(X,Y) is true.
(2) if Y is larger than X and (roughly speaking) hatTeiler(X,Y+1) is true, then hatTeiler(X, Y) is also true.
Rule (1) sounds good, but (2) sounds fishy: for specific X and Y we get e.g.: hatTeiler(4,15) is true when hatTeiler(4,16) is true. If I understand correctly, this problem is about divisors so I would not expect this property to hold. Moreover, the backwards reasoning of prolog will then try to deduce hatTeiler(4,17), hatTeiler(4,18), etc. which leads to non-termination. I guess you want the cut to stop the recursion but it looks like you need a different property.
Coming from the original property, you want to check if X = N * B for some N and B. We know that 2 <= N <= X and X mod N = 0. For the first one there is even a built-in called between/2 that makes the whole thing a two-liner:
hT(X,B) :-
between(2, X, B),
0 is (X mod B).
?- hT(12,X).
X = 2 ;
X = 3 ;
X = 4 ;
X = 6 ;
X = 12.
Now you only need to write your own between and you're done - all without cuts.
(*) The more general hasTeiler(X,X) fails because is (and <) only works when the right hand side (both sides) is variable-free and contains only arithmetic terms (i.e. numbers, +, -, etc).
If you put cut before the fail, it will be freeze the backtracking.
The cut operation freeze the backtracking , if prolog cross it.
Actually when prolog have failed, it backtracks to last cut.
for example :
a:- b,
c,!,
d,
e,!,
f.
Here, if b or c have failed, backtrack do not freeze.
if d or f have failed, backtrack Immediately freeze, because before it is a cut
if e have failed , it can backtrack just on d
I hope it be useful

Better termination for s(X)-sum

(Let me sneak that in within the wave of midterm questions.)
A common definition for the sum of two natural numbers is nat_nat_sum/3:
nat_nat_sum(0, N, N).
nat_nat_sum(s(M), N, s(O)) :-
nat_nat_sum(M, N, O).
Strictly speaking, this definition is too general, for we have now also success for
?- nat_nat_sum(A, B, unnatural_number).
Similarly, we get the following answer substitution:
?- nat_nat_sum(0, A, B).
A = B.
We interpret this answer substitution as including all natural numbers and do not care about other terms.
Given that, now lets consider its termination property. In fact, it suffices to consider the following failure slice. That is, not only will nat_nat_sum/3 not terminate, if this slice does not terminate. This time they are completely the same! So we can say iff.
nat_nat_sum(0, N, N) :- false.
nat_nat_sum(s(M), N, s(O)) :-
nat_nat_sum(M, N, O), false.
This failure slice now exposes the symmetry between the first and third argument: They both influence non-termination in exactly the same manner! So while they describe entirely different things — one a summand, the other a sum — they have exactly the same influence on termination. And the poor second argument has no influence whatsoever.
Just to be sure, not only is the failure slice identical in its common termination condition
(use cTI) which reads
nat_nat_sum(A,B,C)terminates_if b(A);b(C).
It also terminates exactly the same for those cases that are not covered by this condition, like
?- nat_nat_sum(f(X),Y,Z).
Now my question:
Is there an alternate definition of nat_nat_sum/3 which possesses the termination condition:
nat_nat_sum2(A,B,C) terminates_if b(A);b(B);b(C).
(If yes, show it. If no, justify why)
In other words, the new definition nat_nat_sum2/3 should terminate if already one of its arguments is finite and ground.
Fine print. Consider only pure, monotonic, Prolog programs. That is, no built-ins apart from (=)/2 and dif/2
(I will award a 200 bounty on this)
nat_nat_sum(0, B, B).
nat_nat_sum(s(A), B, s(C)) :-
nat_nat_sum(B, A, C).
?
Ok, seems its over. The solution I was thinking of was:
nat_nat_sum2(0, N,N).
nat_nat_sum2(s(N), 0, s(N)).
nat_nat_sum2(s(N), s(M), s(s(O))) :-
nat_nat_sum2(N, M, O).
But as I realize, that's just the same as #mat's one which is almost the same as #WillNess'es.
Is this really the better nat_nat_sum/3? The original's runtime is independent of B (if we ignore one (1) occurs check for the moment).
There is another downside of my solution compared to #mat's solution which naturally extends to nat_nat_nat_sum/3
nat_nat_nat_sum(0, B, C, D) :-
nat_nat_sum(B, C, D).
nat_nat_nat_sum(s(A), B, C, s(D)) :-
nat_nat_nat_sum2(B, C, A, D).
Which gives
nat_nat_nat_sum(A,B,C,D)terminates_if b(A),b(B);b(A),b(C);b(B),b(C);b(D).
(provable in the unfolded version
with cTI)
The obvious trick is to flip the arguments:
sum(0,N,N).
sum(N,0,N).
sum(s(A),B,s(C)):- sum(B,A,C) ; sum(A,B,C).
Take the following two definitions:
Definition 1:
add(n,X,X).
add(s(X),Y,s(Z)) :- add(X,Y,Z).
Definition 2:
add(n,X,X).
add(s(X),Y,Z) :- add(X,s(Y),Z).
Definition 1 terminates for pattern add(-,-,+), whereas definition 2
does not terminate for pattern add(-,-,+). Look see:
Definition 1:
?- add(X,Y,s(s(s(n)))).
X = n,
Y = s(s(s(n))) ;
X = s(n),
Y = s(s(n)) ;
X = s(s(n)),
Y = s(n) ;
X = s(s(s(n))),
Y = n
?-
Definition 2:
?- add(X,Y,s(s(s(n)))).
X = n,
Y = s(s(s(n))) ;
X = s(n),
Y = s(s(n)) ;
X = s(s(n)),
Y = s(n) ;
X = s(s(s(n))),
Y = n ;
Error: Execution aborted since memory threshold exceeded.
add/3
add/3
?-
So I guess definition 1 is better than definition 2.
Bye

Prolog Use of Cuts

I am re-writing the following function in Prolog:
V1:
f(X,Y):- X < 2, Y is X+1.
f(X,3):- 2 =< X, X < 5.
f(X,Y):- 5 =< X, Y is 8-X.
As V2:
f(X,Y) :-
X < 2,
Y is X + 1.
f(X,Y) :-
X >= 2,
X < 5,
Y is 3.
f(X,Y) :-
X >= 5,
Y is 8-X.
I then wanted to experiment with cuts. For green cuts (V3):
f(X,Y) :-
X < 2, !,
Y is X + 1.
f(X,Y) :-
X >= 2,
X < 5, !,
Y is 3.
f(X,Y) :-
X >= 5,
Y is 8-X.
For red cuts (V4):
f(X,Y) :-
X < 2, !,
Y is X + 1.
f(X,Y) :-
X < 5, !,
Y is 3.
f(X,Y) :-
Y is 8-X.
However, I don't understand their advantage, as deleting the cuts would allow the same behaviour of the code... Any help?
All your versions V1..V4 are observationally equivalent, so you got some reasoning right. Still, there are differences.
Avoiding superfluous choice points
In many implementations, V1 and V2 might be particularly less efficient, for, internally, they "leave open a choice point". This is so because such Prologs do not look any further to the other rules. So each goal f(1,X) consumes a bit of memory that can be freed only on backtracking (or using !). Here is a simple way to try this out yourself:
loop(Goal) :-
Goal,
loop(Goal).
Here is what I get in SWI:
?- time(loop(f1(1,2))).
% 5,991,554 inferences, 81.282 CPU in 81.443 seconds (100% CPU, 73713 Lips)
ERROR: Out of local stack
?- time(loop(f2(1,2))).
% 5,991,553 inferences, 85.032 CPU in 85.212 seconds (100% CPU, 70462 Lips)
ERROR: Out of local stack
Whereas V3 and V4 seem to run indefinitely - at least much longer than 85s. Experiments such as this one are funny for very tiny programs but are not very practical for bigger ones. Fortunately, there is a simple way to tell in many Prologs whether or not a query is executed determinately. To see if your system does this, enter:
?- X = 1.
X = 1.
For your variations:
?- f1(1,2).
true
; % <== Prolog asked for another answer
false. % <== only to conclude that there is none.
?- f2(1,2).
true
; false. % same again
?- f3(1,2).
true. % <== Prolog knows there will be no further answer
?- f4(1,2).
true.
Avoiding recalculations - making cuts red
While V3 avoids superfluous choice points, V4 now even avoids superfluous calculations. So it should be the most efficient. But it comes at the price of fixing the order of the clauses.
However, V3 was only possible, because two necessary conditions for green cuts coincided:
Non-overlapping conditions. That should be obvious to you.
Safe testing of instantiations. This is far from obvious. Please note that the goal X < 2 has an implicit test for a correct instantiation attached! It produces an instantiation error should X be an uninstantiated variable. It is because of this very test that the cut in V3 happens to be a green cut. Without that testing, it would be a red cut.
Note also that V1 and V2 would not be equivalent, if the second rule would be alone! For the goal f(X,5). would fail in V1 but it would produce an error in V2.
As you noted the first version shows green cuts and the second red cuts.
It is not necessary that you will feel the difference between these two versions.
a) one reason can be efficiency, but for toy codes with fast machines you hardly notice it.
b) shuffling the rules should not change code's behavior in case of green cuts, and that's true for the first code. But in the second code, if you put the second clause before the first one than the behavior changes: f(0,3) is true, but initially it was false. Therefore you would feel difference if you shuffle the rules.
Advantage of shuffling is that you don't care about order but content - that's one of the points declarative programing.

Finding query for which a prolog program gives incorrect result

This Prolog program defines the third argument to be the maximum value of the first two numeric arguments:
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y).
I think that this program works just fine. But I am told that it can give incorrect result. Can you tell when and why?
This is a textbook example.
?- max(5,1,1).
true.
Homework: Why is the program wrong? How do we make the program correct?
EDIT
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y).
Our intention is to say:
If X is greater than Y, then Max is X. Otherwise, Max must be Y.
Instead, what is say is:
When the first and third arguments (X and Max) can be unified, and X is greater than Y, succeed. Otherwise, if the second and third arguments (Y and Max) can be unified, succeed.
The obvious problem arises then the first and third arguments cannot be unified, but the second and the third can.
Instead:
max(X, Y, X) :- X >= Y.
max(X, Y, Y) :- X < Y.
or
max(X, Y, Max) :- X >= Y, !, Max = X.
max(_, Max, Max).
It does work fine, provided the third argument is uninstantiated. The danger here would be if there were a way to backtrack into the second rule, or if the third argument is instantiated to the same value as the second. It's not particularly safe looking because max(X, Y, Y). is equal to max(_, Y, Y) which just sets the result to the second value without any thought. The cut at the end of the first rule effectively ensures that backtracking will not commence if X >= Y, so the second rule should only be entered when X < Y and Z is not already equal to Y.
Though it mostly works, it's not a good habit to get into. People new to Prolog tend to think procedurally and making use of the cut like this to ensure a particular result through procedural trickery ultimately holds you back and leads to convoluted Prolog that cannot be driven in different and interesting ways. There are several other ways of writing this predicate that work just as well but do not rely on the cut to ensure their behavior, for instance:
max(X, Y, X) :- X >= Y.
max(X, Y, Y) :- X < Y.
or
max(X, Y, Z) :- X >= Y -> Z = X ; Z = Y.
Neither of these is vulnerable to the problem of the third being instantiated. Interestingly, this is a great illustration of the difference between a red cut and a green cut. Your code has a red cut, where the behavior is dependent on the cut, but if I simply change my first solution to this:
max(X, Y, X) :- X >= Y, !.
max(X, Y, Y) :- X < Y.
That's a green cut, because the behavior is not dependent on the cut, but Prolog's performance may improve slightly since it won't backtrack into the second clause to try it. Here we're explicitly telling Prolog, don't both making the next check because we know it will fail. With a red cut, there's no other check which will fail.
It's unfortunate that stating the condition twice feels redundant but relying on a single rule feels clunky. In practice, my experience is that scenarios like these are not ultimately all that common; usually you have atoms or structures you can match in the head of the clause that create behavior like we have in my first substitute, but without needing a body. For example:
perform(scan(target, X, Y)) :- ...
perform(scan(calibration, X)) :- ...
This has the same effect: Prolog will backtrack until it unifies successfully, then it will back track again, but the exclusive nature of the matching will prevent another body from being executed. If we find out it's spending too much time backtracking we can add cuts to improve the performance, but in practice it's unlikely to be a problem.

Resources