Understanding Trace* - wolfram-mathematica

Good day,
When trying to understand the Mathematica's evaluation sequence by using standard Trace and TraceScan commands and their nice visual representations developed in the recent thread, I faced some ambiguities in their behavior.
First of all, when I evaluate
In[1]:= Trace[a+1,TraceOriginal->True]
I get
Out[1]=
{a+1,{Plus},{a},{1},a+1,1+a,{Plus},{1},{a},1+a}
All sublists correspond to sub-evaluations (as it is stated in the Documentation). The last expression 1+a probably corresponds to the result of the evaluation although it is not clearly stated in the Documentation. But what exactly mean expressions a+1 and 1+a in the middle of the list? To which evaluation steps of the standard evaluation sequence they correspond?
The second oddity is with TraceScan. Consider the following:
In[1]:= list={}; TraceScan[AppendTo[list,StyleForm[#,"Input"]]&,(a+1),_,AppendTo[list,#]&]; list
Out[1]=
{a+1, Plus, Plus, a, a, 1, 1, 1+a, Plus, Plus, 1, 1, a, a, 1+a, a+1}
You can see that the last two expressions in the list are 1+a and a+1. Both are results of (sub)evaluations. But the real output is 1+a and so I do not understand why a+1 is at the end of the evaluation chain? And why is no there a+1 in the middle of the evaluation chain as it was in the case of Trace? Is it a bug?
P.S. These results are reproduced with Mathematica 7.0.1 and 5.2.

The fp argument to TraceScan is called with two arguments. The first is the original unevaluated expression. The second is the result of the evaluation. In your example, the second AppendTo is using the first argument so you are seeing the unevaluated expression. Change # to #2 and then you will see the results you expect.
Also note that the second argument is not wrapped in HoldForm (documentation notwithstanding), so in general one must take care to use a function that holds its arguments for the fp argument to avoid generating spurious evaluations.
Comparing Trace and TraceScan
The behaviour of Trace is described in considerable detail in the Mathematica 8 documentation. It states that, by default, Trace only shows expressions after the head and arguments have been evaluated. Thus, we see a sequence like this:
In[28]:= SetAttributes[f, Orderless]
Trace[f[a, 1]]
Out[29]= {f[a,1],f[1,a]}
Only the input expression and its result is shown. The TraceOriginal options controls (quote) "whether to look at expressions before their heads and arguments are evaluated". When this option is True then the output is supplemented with the head and argument expressions:
In[30]:= Trace[f[a,1], TraceOriginal->True]
Out[30]= {f[a,1],{f},{a},{1},f[a,1],f[1,a]}
The first element of the new list is the original expression before the head and arguments are evaluated. Then we see the head and arguments being evaluated. Finally, we see the top-level expressions again, after the head and arguments have been evaluated. The last two elements of the list match the two elements of the original trace output.
As the linked documentation states, Trace is very selective about what expressions it returns. For example, it omits trivial evaluation chains completely. TraceScan is comprehensive and calls the supplied functions for every evaluation, trivial or not. You can see the comprehensive set of evaluations using the following TraceScan expression:
TraceScan[Print, f[a,1], _, Print[{##}]&]
The following table matches the output produced by Trace with and without TraceOriginal, along with the output of the TraceScan expression:
Trace Trace TraceScan
Original
f[a,1] f[a,1]
f
{f} {f
,f}
a
{a} {a
,a}
1
{1} {1
,1}
f[1,a]
{f[1,a]
,f[1,a]}
f[a,1] f[a,1] {f[a,1]
f[1,a] f[1,a] ,f[1,a]}
There is certain amount of speculation in this table about which entry matches against which, given that the internals of Trace are inaccessible. Further experiments might give information that adjusts the alignment. However, the key point is that all of the information generated by Trace is available using TraceScan -- and TraceScan provides more.

The first part of the question is easy.
The expressions a+1 and 1+a in the middle of the list are where the Orderless attribute of Plus fires and the terms are arranged in the default order.
This is point number 8 in the standard evaluation sequence tute.
The "strangeness" in TraceScan also occurs in version 8.
Because, it's a rare command, here's the documentation for TraceScan
TraceScan[f, expr, form, fp] applies f
before evaluation and fp after
evaluation to expressions used in the
evaluation of expr.
Note that if you apply it to the expression a + 1 + b you get
In[32]:= TraceScan[Print["f \t",#]&, a+1+b, _, Print["fp\t",#]&]
During evaluation of In[32]:= f a+1+b
During evaluation of In[32]:= f Plus
During evaluation of In[32]:= fp Plus
During evaluation of In[32]:= f a
During evaluation of In[32]:= fp a
During evaluation of In[32]:= f 1
During evaluation of In[32]:= fp 1
During evaluation of In[32]:= f b
During evaluation of In[32]:= fp b
During evaluation of In[32]:= f 1+a+b
During evaluation of In[32]:= fp 1+a+b
During evaluation of In[32]:= fp a+1+b
Out[32]= 1+a+b
From here, it's clear what's happening. fp applies after the evaluation - so the final fp actually corresponds to the first f. It doesn't print until the very end because the subexpressions need to be evaluated first.

Related

Prolog-Splitting an algebraic expression using only "+" into separate lists of atoms and numbers

I am trying to create a predicate split_exp, which takes an algebraic expression that uses only "+" functor and generates two lists, one of the atoms and one of numbers. For example, 1+a+b+3 should generate [1,3] and [a,b]. The expression can be of any length.
I have tried.
split_exp(X,[X|[]],[]):-number(X).
split_exp(X,[],[X|[]]):-atom(X).
split_exp(X+Y,[X|Ns],Na):-number(X),split_exp(Y,Ns,Na).
split_exp(X+Y,Ns,[X|Na]):-atom(X),split_exp(Y,Ns,Na).
Kindly explain to me where i am going wrong in this implementation.
Should be easy to spot from your code, corrected and simplified:
split_exp(X,[X],[]):-number(X).
split_exp(X,[],[X]):-atom(X).
split_exp(Y+X,[X|Ns],As):-number(X),split_exp(Y,Ns,As).
split_exp(Y+X,Ns,[X|As]):-atom(X),split_exp(Y,Ns,As).
Note the recursive call, still using Y. Since (+)/2 is left associative (?- current_op(_,A,+) yields A=yfx as second result, since there is also the unary form), you must recurse on the left branch, which I did swapping X and Y subterms in the head(s).
You can show the expression shape using the ISO builtin ?- write_canonical(E) or ?- display(E).

Prolog - Towers of Hanoi

I'm trying to write a program to solve the towers of hanoi problem in Prolog. None of the posts here helped me so I decided to ask for myself. I have written the following code. It works well for 2 disks but goes into an infinite loop for 3.
hanoi(1,A,B,_,[(A,B)]).
hanoi(X,A,B,C,Y):-
hanoi(X2,A,C,B,Y1),
hanoi(1,A,B,_,[Y2]),
hanoi(X2,C,B,A,Y3),
append(Y1,[Y2|Y3],Y),
X2 is X-1.
It is called in the following way:
?- hanoi(3, a, b, c, Y).
a,b,c are the pegs. 3 is the number of disks and X is where we want the result.
I need to get the result in Y. I'm trying to recursively find the moves for X-1 discs from peg 1 to 3 using 2, 1 disc from peg 1 to 2, X-1 discs from peg 3 to 2 and append them. I can't understand what I'm doing wrong. Any help or guidance would be appreciated! Thanks!
The obvious problem -
When you have a conjunction, like:
a, b, c
You have two readings of this, logical and procedural. The logical reading would be:
"The conjunction is true if a is true, and b is true, and c is true."
The procedural reading is:
"The conjunction will succeed if a is evaluated and succeeds, then b is evaluated and it also succeeds, and then c is evaluated and it also succeeds." (or, to put it in other words, do a depth-first search of the solution space)
If you are careful enough, the procedural reading is not necessary to argue about your code. However, this is only the case when all subgoals in the conjunction have full overlap of the logical and the procedural reading.
The offender here is is/2. It does not have a purely logical meaning. It evaluates the right-hand operand (the arithmetic expression) and unifies the result with the left-hand (usually an unbound variable).
Here, you have a conjunction (in the body of the second clause) that says, in effect, "evaluate a(X), then, if it succeeds, find the value of X". The obvious problem is that a(X) requires the value of X to be evaluated in such a way that it terminates.
So, move the is before all subgoals that use its result, or look into using constraints.

Explanation of Prolog recursive procedure

I'd like someone to explain this procedure if possible (from the book 'learn prolog now'). It takes two numerals and adds them together.
add(0,Y,Y).
add(s(X),Y,s(Z)) :- add(X,Y,Z).
In principle I understand, but I have a few issues. Lets say I issue the query
?- add(s(s(0)), s(0), R).
Which results in:
R = s(s(s(0))).
Step 1 is the match with rule 2. Now X becomes s(0) and Y is still s(0). However Z (according to the book) becomes s(_G648), or s() with an uninstantiated variable inside it. Why is this?
On the final step the 1st rule is matched which ends the recursion. Here the contents of Y somehow end up in the uninstantiated part of what was Z! Very confusing, I need a plain english explanation.
First premises:
We have s(X) defined as the successor of X so basically s(X) = X+1
The _G### notation is used in the trace for internal variables used for the recursion
Let's first look at another definition of addition with successors that I find more intuitive:
add(0,Y,Y).
add(s(A),B,C) :- add(A,s(B),C).
this does basically the same but the recursion is easier to see:
we ask
add(s(s(0)),s(0),R).
Now in the first step prolog says thats equivalent to
add(s(0),s(s(0)),R)
because we have add(s(A),B,C) :- add(A,s(B),C) and if we look at the question A = s(0) and B=s(0). But this still doesn't terminate so we have to reapply that equivalency with A=0 and B=s(s(0)) so it becomes
add(0,s(s(s(0))),R)
which, given add(0,Y,Y). this means that
R = s(s(s(0)))
Your definition of add basically does the same but with two recursions:
First it runs the first argument down to 0 so it comes down to add(0,Y,Y):
add(s(s(0)),s(0),R)
with X=s(0), Y = s(0) and s(Z) = R and Z = _G001
add(s(0),s(0),_G001)
with X = 0, Y=s(0) and s(s(Z)) = s(G_001) = R and Z = _G002
add(0,s(0),_G002)
So now it knows that _G002 is s(0) from the definition add(0,Y,Y) but has to trace its steps back so _G001 is s(_G002) and R is s(_G001) is s(s(_G002)) is s(s(s(0))).
So the point is in order to get to the definition add(0,Y,Y) prolog has to introduce internal variables for a first recursion from which R is then evaluated in a second one.
If you want to understand the meaning of a Prolog program, you might concentrate first on what the relation describes. Then you might want to understand its termination properties.
If you go into the very details of a concrete execution as your question suggests, you will soon be lost in the multiplicity of details. After all, Prolog has two different interlaced control flows (AND- and OR-control) and in addition to that it has unification which subsumes parameter passing, assignment, comparison, and equation solving.
Brief: While computers execute a concrete query effortlessly for zillions of inferences, you will get tired after a screenful of them. You can't beat computers in that. Fortunately, there are better ways to understand a program.
For the meaning, look at the rule first. It reads:
add(s(X),Y,s(Z)) :- add(X,Y,Z).
See the :- in between? It is meant to symbolize an arrow. It is a bit unusual that the arrow points from right-to-left. In informal writing you would write it rather left-to-right. Read this as follows:
Provided, add(X,Y,Z) is true, then also add(s(X),Y,s(Z)) is true.
So we assume that we have already some add(X,Y,Z) meaning "X+Y=Z". And given that, we can conclude that also "(X+1)+Y=(Z+1)" holds.
After that you might be interested to understand it's termination properties. Let me make this very brief: To understand it, it suffices to look at the rule: The 2nd argument is only handed further on. Therefore: The second argument does not influence termination. And both the 1st and 3rd argument look the same. Therefore: They both influence termination in the same manner!
In fact, add/3 terminates, if either the 1st or the 3rd argument will not unify with s(_).
Find more about it in other answers tagged failure-slice, like:
Prolog successor notation yields incomplete result and infinite loop
But now to answer your question for add(s(s(0)), s(0), R). I only look at the first argument: Yes! This will terminate. That's it.
Let's divide the problem in three parts: the issues concerning instantiation of variables and the accumulator pattern which I use in a variation of that example:
add(0,Y,Y).
add(s(X),Y,Z):-add(X,s(Y),Z).
and a comment about your example that uses composition of substitutions.
What Prolog applies in order to see which rule (ie Horn clause) matches (whose head unifies) is the Unification Algorithm which tells, in particular, that if I have a variable, let's say, X and a funtor, ie, f(Y) those two term unify (there is a small part about the occurs check to...check but nevermind atm) hence there is a substitution that can let you convert one into another.
When your second rule is called, indeed R gets unified to s(Z). Do not be scared by the internal representation that Prolog gives to new, uninstantiated variables, it is simply a variable name (since it starts with '_') that stands for a code (Prolog must have a way to express constantly newly generated variables and so _G648, _G649, _G650 and so on).
When you call a Prolog procedure, the parameters you pass that are uninstantiated (R in this case) are used to contain the result of the procedure as it completes its execution, and it will contain the result since at some point during the procedure call it will be instantied to something (always through unification).
If at some point you have that a var, ie K is istantiated to s(H) (or s(_G567) if you prefer), it is still partilally instantiated and to have your complete output you need to recursively instantiate H.
To see what it will be instantiated to, have a read at the accumulator pattern paragraph and the sequent one, tho ways to deal with the problem.
The accumulator pattern is taken from functional programming and, in short, is a way to have a variable, the accumulator (in my case Y itself), that has the burden to carry the partial computations between some procedure calls. The pattern relies on recursion and has roughly this form:
The base step of the recursion (my first rule ie) says always that since you have reached the end of the computation you can copy the partial result (now total) from your accumulator variable to your output variable (this is the step in which, through unification your output var gets instantiated!)
The recursive step tells how to create a partial result and how to store it in the accumulator variable (in my case i 'increment' Y). Note that in the recursive step the output variable is never changed.
Finally, concerning your exemple, it follows another pattern, the composition of substitutions which I think you can understand better having thought about accumulator and instantiation via unification.
Its base step is the same as the accumulator pattern but Y never changes in the recursive step while Z does
It uses to unify the variable in Z with Y by partially instantiating all the computation at the end of each recursive call after you've reached the base step and each procedure call is ending. So at the end of the first call the inner free var in Z has been substituted by unification many times by the value in Y.
Note the code below, after you have reached the bottom call, the procedure call stack starts to pop and your partial vars (S1, S2, S3 for semplicity) gets unified until R gets fully instantiated
Here is the stack trace:
add(s(s(s(0))),s(0),S1). ^ S1=s(S2)=s(s(s(s(0))))
add( s(s(0)) ,s(0),S2). | S2=s(S3)=s(s(s(0)))
add( s(0) ,s(0),S3). | S3=s(S4)=s(s(0))
add( 0 ,s(0),S4). | S4=s(0)
add( 0 ,s(0),s(0)). ______|

OTTER inferences

I'm writing an input file for OTTER that is very simple:
set(auto).
formula_list(usable).
all x y ([Nipah(x) & Encephalitis(y)] -> Causes(x,y)).
exists x y (Nipah(x) & Encephalitis(y)).
end_of_list.
I get this output for the search :
given clause #1: (wt=2) 2 [] Nipah($c2).
given clause #2: (wt=2) 2 [] Encephalitis($c1).
search stopped because sos empty
Why won't OTTER infer Causes($c2,$c1)?
EDIT:
I removed the square brackets from [Nipah(x) & Encephalitis(x)] and it worked. Why does this matter?
I'd answer with a question: Why did you use square brackets in the first place?
Look into Otter manual, Section 4.3, List Notation. Square brackets are used for lists, it's syntactic sugar that is expanded into special terms. In your case, it expanded to something like
all x y ($cons(Nipah(x) & Encephalitis(y), $nil) -> Causes(x,y)).
Why won't OTTER infer Causes($c2,$c1)?
Note that the resolution calculus is not complete in the sense that every formula provable in a given theory could be inferred by the calculus. This would be highly undesirable! Instead, resolution is only refutationally complete, meaning that if a given theory is
contradictory then the resolution will find a proof of the empty clause. So even if a clause C is a logical consequence of a set of clauses T, it doesn't mean that the resolution calculus can derive C from T. In your case, the fact that Causes($c2,$c1) follows from the input doesn't mean Otter has to derive it.

prolog - infinite rule

I have the next rules
% Signature: natural_number(N)/1
% Purpose: N is a natural number.
natural_number(0).
natural_number(s(X)) :-
natural_number(X).
ackermann(0, N, s(N)). % rule 1
ackermann(s(M),0,Result):-
ackermann(M,s(0),Result). % rule 2
ackermann(s(M),s(N),Result):-
ackermann(M,Result1,Result),
ackermann(s(M),N,Result1). % rule 3
The query is: ackermann (M,N,s(s(0))).
Now, as I understood, In the third calculation, we got an infinite search (failure branch). I check it, and I got a finite search (failure branch).
I'll explain: In the first, we got a substitution of M=0, N=s(0) (rule 1 - success!). In the second, we got a substitution of M=s(0),N=0 (rule 2 - success!). But what now? I try to match M=s(s(0)) N=0, But it got a finite search - failure branch. Why the compiler doesn't write me "fail".
Thank you.
It was a bit hard to understand exactly what Tom is asking here. Perhaps there's an expectation that the predicate natural_number/1 somehow influences the execution of ackermann/3. It will not. The latter predicate is purely recursive and makes no subgoals that depend on natural_number/1.
When the three clauses shown are defined for ackermann/3, the goal:
?- ackermann(M,N,s(s(0))).
causes SWI-Prolog to find (with backtracking) the two solutions that Tom reports, and then to go into infinite recursion (resulting in an "Out of Stack" error). We can be sure that this infinite recursion involves the third clause given for ackermann/3 (rule 3 per Tom's comments in code) because in its absence we only get the two acknowledged solutions and then explicit failure:
M = 0,
N = s(0) ;
M = s(0),
N = 0 ;
false.
It seems to me Tom asks for an explanation of why changing the submitted query to one that sets M = s(s(0)) and N = 0, producing a finite search (that finds one solution and then fails on backtracking), is consistent with the infinite recursion produced by the previous query. My suspicion here is that there's a misunderstanding of what the Prolog engine attempts in backtracking (for the original query), so I'm going to drill down on that. Hopefully it clears up the matter for Tom, but let's see if it does. Admittedly my treatment is wordy, but the Prolog execution mechanism (unification and resolution of subgoals) is worthy of study.
[Added: The predicate has an obvious connection to the famous Ackermann function that is total computable but not primitive recursive. This function is known for growing rapidly, so we need to be careful in claiming infinite recursion because a very large but finite recursion is also possible. However the third clause puts its two recursive calls in an opposite order to what I would have done, and this reversal seems to play a critical role in the infinite recursion we find in stepping through the code below.]
When the top-level goal ackermann(M,N,s(s(0))) is submitted, SWI-Prolog tries the clauses (facts or rules) defined for ackermann/3 until it finds one whose "head" unifies with the given query. The Prolog engine does not have far to look as the first clause, this fact:
ackermann(0, N, s(N)).
will unify, binding M = 0 and N = s(0) as has already been described as the first success.
If requested to backtrack, e.g. by user typing semi-colon, the Prolog engine checks to see if there is an alternative way to satisfy this first clause. There is not. Then the Prolog engine proceeds to attempt the following clauses for ackermann/3 in their given order.
Again the search does not have to go far because the second clause's head also unifies with the query. In this case we have a rule:
ackermann(s(M),0,Result) :- ackermann(M,s(0),Result).
Unifying the query and the head of this rule yields the bindings M = s(0), N = 0 in terms of the variables used in the query. In terms of the variables used in the rule as stated above, M = 0 and Result = s(s(0)). Note that unification matches terms by their appearance as calling arguments and does not consider variable names reused across the query/rule boundary as signifying identity.
Because this clause is a rule (having body as well as head), unification is just the first step in trying to succeed with it. The Prolog engine now attempts the one subgoal that appears in the body of this rule:
ackermann(0,s(0),s(s(0))).
Note that this subgoal comes from replacing the "local" variables used in the rule by the values of unification, M = 0 and Result = s(s(0)). The Prolog engine is now calling the predicate ackermann/3 recursively, to see if this subgoal can be satisfied.
It can, as the first clause (fact) for ackermann/3 unifies in the obvious way (indeed in essentially the same way as before as regards the variables used in the clause). And thus (upon this recursive call succeeding), we get the second solution succeeding in the outer call (the top-level query).
If the user asks the Prolog engine to backtrack once more, it again checks to see if the current clause (the second one for ackermann/3) can be satisfied in an alternative way. It cannot, and so the search continues by passing to the third (and last) clause for predicate ackermann/3:
ackermann(s(M),s(N),Result) :-
ackermann(M,Result1,Result),
ackermann(s(M),N,Result1).
I'm about to explain that this attempt does produce infinite recursion. When we unify the top-level query with the head of this clause, we get bindings for the arguments that can perhaps be clearly understood by aligning the terms in in the query with those in the head:
query head
M s(M)
N s(N)
s(s(0)) Result
Bearing in mind that variables having the same name in the query as variables in the rule does not constrain unification, this triple of terms can be unified. Query M will be head s(M), that is a compound term involving functor s applied to some as-yet unknown variable M appearing in the head. Same thing for query N. The only "ground" term so far is variable Result appearing in the head (and body) of the rule, which has been bound to s(s(0)) from the query.
Now the third clause is a rule, so the Prolog engine must continue by attempting to satisfy the subgoals appearing in the body of that rule. If you substitute values from the head unification into the body, the first subgoal to satisfy is:
ackermann(M,Result1,s(s(0))).
Let me point out that I've used here the "local" variables of the clause, except that I've replaced Result by the value to which it was bound in unification. Now notice that apart from replacing N of the original top-level query by variable name Result1, we are just asking the same thing as the original query in this subgoal. Certainly it's a big clue we might be about to enter an infinite recursion.
However a bit more discussion is needed to see why we don't get any further solutions reported! This is because the first success of that first subgoal is (just as found earlier) going to require M = 0 and Result1 = s(0), and the Prolog engine must then proceed to attempt the second subgoal of the clause:
ackermann(s(0),N,s(0)).
Unfortunately this new subgoal does not unify with the first clause (fact) for ackermann/3. It does unify with the head of the second clause, as follows:
subgoal head
s(0) s(M)
N 0
s(0) Result
but this leads to a sub-subgoal (from the body of the second clause):
ackermann(0,s(0),s(0)).
This does not unify with the head of either the first or second clause. It also does not unify with the head of the third clause (which requires the first argument to have the form s(_)). So we reached a point of failure in the search tree. The Prolog engine now backtracks to see if the first subgoal of the third clause's body can be satisfied in an alternative way. As we know, it can be (since this subgoal is basically the same as the original query).
Now M = s(0) and Result1 = 0 of that second solution leads to this for the second subgoal of the third clause's body:
ackermann(s(s(0)),N,0).
While this does not unify with the first clause (fact) of the predicate, it does unify with the head of the second clause:
subgoal head
s(s(0)) s(M)
N 0
0 Result
But in order to succeed the Prolog engine must satisfy the body of the second clause as well, which is now:
ackermann(s(s(0)),s(0),0).
We can easily see this cannot unify with the head of either the first or second clause for ackermann/3. It can be unified with the head of the third clause:
sub-subgoal head(3rd clause)
s(s(0)) s(M)
s(0) s(N)
0 Result
As should be familiar now, the Prolog engine checks to see if the first subgoal of the third clause's body can be satisfied, which amounts to this sub-sub-subgoal:
ackermann(s(0),Result1,0).
This fails to unify with the first clause (fact), but does unify with the head of the second clause binding M = 0, Result1 = 0 and Result = 0 , producing (by familiar logic) the sub-sub-sub-subgoal:
ackermann(0,0,0).
Since this cannot be unified with any of the three clauses' heads, this fails. At this point the Prolog engine backtracks to trying to satisfy the above sub-sub-subgoal using the third clause. Unification goes like this:
sub-sub-subgoal head(3rd clause)
s(0) s(M)
Result1 s(N)
0 Result
and the Prolog engine's task is then to satisfy this sub-sub-sub-subgoal derived from the first part of the third clause's body:
ackermann(0,Result1,0).
But this will not unify with the head of any of the three clauses. The search for a solution to the sub-sub-subgoal above terminates in failure. The Prolog engine backtracks all the way to where it first tried to satisfy the second subgoal of the third clause as invoked by the original top-level query, as this has now failed. In other words it tried to satisfy it with the first two solutions of the first subgoal of the third clause, which you will recall was in essence the same except for change of variable names as the original query:
ackermann(M,Result1,s(s(0))).
What we've seen above are that solutions for this subgoal, duplicating the original query, from the first and second clauses of ackermann/3, do not permit the second subgoal of the third clause's body to succeed. Therefore the Prolog engine tries to find solutions that satisfy the third clause. But clearly this is now going into infinite recursion, as that third clause will unify in its head, but the body of the third clause will repeat exactly the same search we just chased through. So the Prolog engine now winds up going into the body of the third clause endlessly.
Let me rephrase your question: The query ackermann(M,N,s(s(0))). finds two solutions and then loops. Ideally, it would terminate after these two solutions, as there is no other N and M whose value is s(s(0)).
So why does the query not terminate universally? Understanding this can be quite complex, and the best advice is to not attempt to understand the precise execution mechanism. There is a very simple reason: Prolog's execution mechanism turns out to be that complex that you easily will misunderstand it anyway if you attempt to understand it by stepping through the code.
Instead, you can try the following: Insert goals false at any place in your program. If the resulting program does not terminate, then also the original program will not terminate.
In your case:
ackermann(0, N, s(N)) :- false.
ackermann(s(M),0,Result):- false,
ackermann(M,s(0),Result).
ackermann(s(M),s(N),Result):-
ackermann(M,Result1,Result), false,
ackermann(s(M),N,Result1).
We can now remove the first and second clause. And in the third clause, we can remove the goal after false. So if the following fragment does not terminate, also the original program will not terminate.
ackermann(s(M),s(N),Result):-ackermann(M,Result1,Result), false.
This program now terminates only if the first argument is known. But in our case it's free...
That is: By considering a small fraction of the program (called a failure-slice) we already were able to deduce a property of the entire program. For details, see this paper and others on the site.
Unfortunately, that kind of reasoning only works for cases of non-termination. For termination, things are more complex. The best is to try a tool like cTI which infers termination conditions and tries to prove their optimality. I entered your program already, so try to modify if and see the effects!
If we are at it: This small fragment also tells us that the second argument does not influence termination1. That means, that queries like ackermann(s(s(0)),s(s(0)),R). will
not terminate either. Exchange the goals to see the difference...
1 To be precise, a term that does not unify with s(_) will influence termination. Think of 0. But any s(0), s(s(0)), ... will not influence termination.

Resources