42

I start to learn Prolog and first learnt about the successor notation.

And this is where I find out about writing Peano axioms in Prolog.

See page 12 of the PDF:

sum(0, M, M).
sum(s(N), M, s(K)) :-
    sum(N,M,K).

prod(0,M,0).
prod(s(N), M, P) :-
    prod(N,M,K),
    sum(K,M,P).

I put the multiplication rules into Prolog. Then I do the query:

?- prod(X,Y,s(s(s(s(s(s(0))))))).

Which means finding the factor of 6 basically.

Here are the results.

X = s(0),
Y = s(s(s(s(s(s(0)))))) ? ;
X = s(s(0)),
Y = s(s(s(0))) ? ;
X = s(s(s(0))),
Y = s(s(0)) ? ;
infinite loop

This result has two problems:

  1. Not all results are shown, note that the result X=6,Y=1 is missing.
  2. It does not stop unless I Ctrl+C then choose abort.

So... my questions are:

  1. WHY is that? I tried switching "prod" and "sum" around. The resulting code gives me all results. And again, WHY is that? It still dead-loops though.
  2. HOW to resolve that?

I read the other answer on infinite loop. But I'd appreciate someone answer basing on this scenario. It greatly helps me.

false
  • 10,264
  • 13
  • 101
  • 209
Felastine
  • 793
  • 2
  • 8
  • 18
  • After comparing the tag wikis, [tag:non-termination] seems a much better fit. In fact, judging from the tag wikis, almost all instances where we currently use [tag:infinite-loop] together with [tag:prolog] should actually be tagged instead or in addition with [tag:non-termination]. What is the best place to discuss such tagging best practices? Personally, I prefer [tag:non-termination], at least *in addition* to [tag:infinite-loop]. – mat Mar 21 '16 at 20:39

2 Answers2

36

If you want to study termination properties in depth, programs using are an ideal study object: You know a priori what they should describe, so you can concentrate on the more technical details. You will need to understand several notions.

Universal termination

The easiest way to explain it, is to consider Goal, false. This terminates iff Goal terminates universally. That is: Looking at tracers is the most ineffective way - they will show you only a single execution path. But you need to understand all of them at once! Also never look at answers when you want universal termination, they will only distract you. You have seen it above: You got three neat and correct answers, only then your program loops. So better "turn off" answers with false. This removes all distraction.

Failure slice

The next notion you need is that of a failure slice. Take a pure monotonic logic program and throw in some goals false. If the resulting failure slice does not terminate (universally), also the original program won't. In your exemple, consider:

prod(0,M,0) :- false.
prod(s(N), M, P) :-
    prod(N,M,K), false,
    sum(K,M,P).

These false goals help to remove irrelevant adornments in your program: The remaining part shows you clearly, why prod(X,Y,s(s(s(s(s(s(0))))))). does not terminate. It does not terminate, because that fragment does not care about P at all! You are hoping that the third argument will help to make prod/3 terminate, but the fragment shows you it is all in vain, since P does not occur in any goal. No need for chatty tracers.

Often it is not so easy to find minimal failure slices. But once you found one, it is next to trivial to determine its termination or rather non-termination properties. After some time you can use your intuition to imagine a slice, and then you can use your reason to check if that slice is of relevance or not.

What is so remarkable about the notion of a failure slice is this: If you want to improve the program, you have to modify your program in the part visible in above fragment! As long as you do not change it, the problem will persist. A failure slice is thus a very relevant part of your program.

Termination inference

That is the final thing you need: A termination inferencer (or analyzer) like cTI will help you to identify the termination condition rapidly. Look at the inferred termination conditions of prod/3 and the improved prod2/3 here!


Edit: And since this was a homework question I have not posted the final solution. But to make it clear, here are the termination conditions obtained so far:

    prod(A,B,C)terminates_if b(A),b(B).
    prod2(A,B,C)terminates_if b(A),b(B);b(A),b(C).

So the new prod2/3 is strictly better than the original program!

Now, it is up to you to find the final program. Its termination condition is:

   prod3(A,B,C)terminates_if b(A),b(B);b(C).

To start with, try to find the failure slice for prod2(A,B,s(s(s(s(s(s(0)))))))! We expect it to terminate, but it still does not. So take the program and add manuallyfalse goals! The remaining part will show you the key!

As a final hint: You need to add one extra goal and one fact.


Edit: Upon request, here is the failure slice for prod2(A,B,s(s(s(s(s(s(0))))))):

prod2(0,_,0) :- false.
prod2(s(N), M, P) :-
   sum(M, K, P),
   prod2(N,M,K), false.

sum(0, M, M).
sum(s(N), M, s(K)) :- false,
    sum(N,M,K).

Please note the significantly simplified definition of sum/3. It only says: 0 plus anything is anything. No more. As a consequence even the more specialized prod2(A,0,s(s(s(s(s(s(0))))))) will loop whileprod2(0,X,Y) elegantly terminates ...

false
  • 10,264
  • 13
  • 101
  • 209
  • 1
    By failure slice, it is like negating all the conditions in the rules? And then we do a query like normal? i.e. prod(X,Y,s(s(s(s(s(s(0)))))))? – Felastine Apr 13 '12 at 14:05
  • but prod2(X,Y,s(0)) (or any other value) doesn't terminate either. It's an improvement? – CapelliC Apr 13 '12 at 15:55
  • @Felastine: You add further goals `false/0` somewhere. So you are not negating a condition. Negating, that would be e.g. `dif(X,a)` in place of `X = a`. – false Apr 14 '12 at 12:15
  • @chac: Yes. Please study the termination conditions inferred by cTI - see last link. – false Apr 14 '12 at 12:18
  • 6
    Really great answer. And **Localizing and explaining reasons for non-terminating logic programs with failure-slices** is something I absolutely **need** to grasp. – CapelliC Apr 15 '12 at 16:18
  • @j4nbur53: You are talking about **existential termination**. This notion is very rarely used since it is very brittle. In particular, the conjunction of two existentially terminating goals is not necessarily terminating. However, the conjunction of two universally goals is terminating. – false Aug 20 '16 at 16:56
  • @j4nbur53: Please refer to Plümer, Termination Proofs for Logic Programs, LNAI 446, for the observation about termination of the conjunction. It seems your terminology differs a lot – false Aug 20 '16 at 21:28
  • @j4nbur53: Please note that the terminology of universal and existential termination also corresponds to Marchiori 1996. – false Aug 20 '16 at 21:59
  • Marchiori introduces k-termination, he sets 1-termination=existential terminaton, omega-termination=Example 4.3 and omega+1-termination=universal temination. As an end-user I am less interested in universal termination and more in **Example 4.3**. Can failure-slices help here? –  Aug 20 '16 at 22:39
  • @j4nbur53: Please note that my answer is about universal termination only. I am rather skeptical that failure-slices can help with m-termination for m in 1..ω – false Aug 20 '16 at 22:53
  • But the other answer still has the clauses that you striked out. So how can it be the correct program? – Géry Ogam Aug 03 '21 at 10:22
  • [This](https://stackoverflow.com/a/10214562/772868) is the other answer. It's the only place where `prod3/3` is defined. – false Aug 03 '21 at 10:29
  • Yes I was referring to this answer. And it defines `prod3(0, _, 0).` and `sum(s(N), M, s(K)) :- sum(N, M, K).`, while in your answer you strike out `prod2(0, _, 0).` and `sum(s(N), M, s(K)) :- sum(N, M, K).`, meaning that they are useless clauses. And you say: ‘As a final hint: You need to add one extra goal and one fact.’ So I assumed the correct answer should have different clauses that the ones you striked out. – Géry Ogam Aug 03 '21 at 11:18
  • *useless clauses* they are in the context of non-termination. That's the key idea in failure slices. But they still have some *meaning*; and there they are not useless. – false Aug 03 '21 at 12:33
  • In other words, you mean that the striked out clauses are useful only in the context of termination as they are evaluated, while they are useless in the context of non-termination as they are never reached. – Géry Ogam Aug 03 '21 at 14:00
  • Look, you cannot expect that you do not by yourself read about failure-slices and now get spoon fed every tiny detail. There are [tons, well more than one hundred](https://stackoverflow.com/search?tab=votes&q=%5bfailure-slice%5d%20is%3aa) of related examples here on SO. And, then there is the literature cited above, too. You really need to put an effort on your side first. – false Aug 03 '21 at 14:10
  • I am a beginner in logic programming (taking this online [Stanford course](http://epilog.stanford.edu/logicprogramming/stanford/lessons.php)) and I am genuinely trying to understand *your* post. The fact that it does not provide the correct answer and leave it as an exercise to the reader is already hard enough, so if I cannot even ask you questions it is not very motivating. You claim that Capellic gave the correct answer but the cTI report of his program is `sum(A,B,C)terminates_if b(A);b(C).` and `prod3(A,B,C)terminates_if b(A),b(B);b(A),b(C).` which is clearly wrong for both rules. – Géry Ogam Aug 03 '21 at 15:30
  • `sum(A,B,C)terminates_if b(A);b(C).` is **better** than what you expected. This is under no circumstance a problem! And for `prod3/3` look closely! The systems shows the cases where it is not able to prove termination nor non-termination, Provers are often not as strong as we would like them to be, but cTI at least shows the unresolved cases. And for the exercise to the reader: It is in full source in the other answer with exactly the same name. – false Aug 03 '21 at 18:01
  • @Maggyero: Where does the course demand such programs from you? – false Aug 03 '21 at 19:34
  • To get the expected `sum(A,B,C)terminates_if b(A),b(B);b(C).` in the cTI report, you simply have to replace `sum(N, M, K)` with `sum(M, N, K)` in `sum(s(N), M, s(K)) :- sum(N, M, K).` i.e. switch `N` and `M`. Isn’t it better? This updated `sum` also updates the cTI report of `prod3` to `prod3(A,B,C)terminates_if b(A),b(C).` However I did not find another correction of `prod3` to get the expected `prod3(A,B,C)terminates_if b(A),b(B);b(C).` advertised in your answer. – Géry Ogam Aug 03 '21 at 22:43
  • The course demands such programs in [exercise 9.4](http://epilog.stanford.edu/logicprogramming/exercises/exercise_09_04.html). – Géry Ogam Aug 03 '21 at 22:46
  • But that exercise is not about Prolog! The syntax is different. There is no & for conjunction etc- `number` is coincidentally the same name but `next` isn't known in Prolog at all. – false Aug 04 '21 at 09:39
  • @false The teacher uses Epilog, but the translation into Prolog is trivial: `next/2` is `succ/2` and `&/2` is `,/2`. And since I did not quickly find an online Epilog interpreter on Google, I used Prolog (more precisely the SWI-Prolog implementation of Prolog, available as [SWISH](https://swish.swi-prolog.org/)). – Géry Ogam Aug 04 '21 at 10:08
  • @false Chapter 1: ‘Epilog (the language we use in this volume) is closely related to Datalog and Prolog. Their syntaxes are almost identical. And the three languages are nicely ordered in terms of expressiveness - with Datalog being a subset of Prolog and Prolog being a subset of Epilog. For the sake of simplicity, we use the syntax of Epilog throughout this course, and we talk about the Epilog interpreter and compiler. Thus, when we mention Datalog in what follows, we are referring to the Datalog subset of Epilog; and, when we mention Prolog, we are referring to the Prolog subset of Epilog.’ – Géry Ogam Aug 04 '21 at 10:10
  • @false So is the rule `sum(0, M, M). sum(s(N), M, s(K)) :- sum(M, N, K).` better than `sum(0, M, M). sum(s(N), M, s(K)) :- sum(N, M, K).` since cTI reports `sum(A,B,C)terminates_if b(A),b(B);b(C).` for the former and `sum(A,B,C)terminates_if b(A);b(C).` for the latter? – Géry Ogam Aug 04 '21 at 10:16
  • @Maggyero: This is a question of its own. – false Aug 04 '21 at 10:33
  • @false [Granted](https://stackoverflow.com/q/68655193/2326961). – Géry Ogam Aug 04 '21 at 16:50
17

The first question (WHY) is fairly easy to spot, specially if know about left recursion. sum(A,B,C) binds A and B when C is bound, but the original program prod(A,B,C) doesn't use that bindings, and instead recurse with still A,B unbound.

If we swap sum,prod we get 2 useful bindings from sum for the recursive call:

sum(M, K, P)

Now M is bound, and will be used to terminate the left-recursion. We can swap N and M, because we know that product is commutative.

sum(0, M, M).
sum(s(N), M, s(K)) :-
    sum(N, M, K).

prod3(0, _, 0).
prod3(s(N), M, P) :-
    sum(M, K, P),
    prod3(M, N, K).

Note that if we swap M,K (i.e. sum(K,M,P)), when prod3 is called with P unknown we again have a non terminating loop, but in sum.

?- prod3(X,Y,s(s(s(s(s(s(0))))))).
X = s(s(s(s(s(s(0)))))),
Y = s(0) ;
X = s(s(s(0))),
Y = s(s(0)) ;
X = s(s(0)),
Y = s(s(s(0))) ;
X = s(0),
Y = s(s(s(s(s(s(0)))))) ;
false.

OT I'm perplexed by cTI report: prod3(A,B,C)terminates_if b(A),b(B);b(A),b(C).

CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • concerning cTI: The system tells you that there are `2 unresolved: [prod3(f,b,b),prod3(f,f,b)]`. This means that cTI was not able to infer these cases and NTI was not able to prove nontermination. So it is unknown to cTI. See [the manual](http://www.complang.tuwien.ac.at/cti/help#NTI) – false Apr 18 '12 at 18:30
  • The cTI report of your program is `sum(A,B,C)terminates_if b(A);b(C).` and `prod3(A,B,C)terminates_if b(A),b(B);b(A),b(C).` which is clearly wrong for both rules. I would expect `sum(A,B,C)terminates_if b(A),b(B);b(C).` and `prod3(A,B,C)terminates_if b(A),b(B);b(C).` – Géry Ogam Aug 03 '21 at 15:48
  • @Maggyero: This is a misunderstanding. Look at `sum(0, B,C)` to see that it is in fact terminating. – false Oct 21 '21 at 15:56