2

The following Prolog program defines a predicate fib/2 for computing the Fibonacci number of an integer in successor arithmetics:

fib(0, 0).
fib(s(0), s(0)).
fib(s(s(N)), F) :-
  fib(N, F1),
  fib(s(N), F2),
  sum(F1, F2, F).

sum(0, N, N).
sum(s(N1), N2, s(S)) :-
  sum(N1, N2, S).

It works with queries in this argument mode:

?- fib(s(0), s(0)).
   true
;  false.

It also works with queries in this argument mode:

?- fib(s(0), F).
   F = s(0)
;  false.

It also works with queries in this argument mode:

?- fib(N, F).
   N = F, F = 0
;  N = F, F = s(0)
;  N = s(s(0)), F = s(0)
;  N = s(s(s(0))), F = s(s(0))
;  N = s(s(s(s(0)))), F = s(s(s(0)))
;  …

But it exhausts resources with queries in this argument mode:

?- fib(N, s(0)).
   N = s(0)
;  N = s(s(0))
;
Time limit exceeded

How to implement the Fibonacci sequence in successor arithmetics for all argument modes?

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
  • What have you tried based on your previous questions? – false Aug 21 '21 at 15:38
  • @false I have just posted an [answer](https://stackoverflow.com/a/68877012/2326961) below that follows the same recipe and it seems to work. Is this the right approach in this case? – Géry Ogam Aug 21 '21 at 22:06
  • bien fait. Now, you could improve it (= in another answer, so votes show clear preferences), after all while this bound guarantees termination, it soon runs out of space. – false Aug 22 '21 at 05:18
  • This really requires a bounty... – false Aug 22 '21 at 05:19
  • @false Thanks! The question will be eligible for bounty tomorrow. I will start one when it is available. I guess that you intend to write a tail recursive version. – Géry Ogam Aug 22 '21 at 12:56
  • @false Bounty started! =) – Géry Ogam Aug 23 '21 at 14:28
  • Oh, that was a misunderstanding. By *requires a bounty* I meant that I wanted to spend one... Next round, it's me – false Aug 23 '21 at 17:32
  • @false My bad, you wanted me to post another answer that does not soon run out of space and you would have awarded a bounty to me. Since I don’t have the solution anyway, you can still post the improved answer that you had in mind so that I can award you the bounty. – Géry Ogam Aug 23 '21 at 18:09
  • Anyway, start to figure out how you could improve it! First measure it to see where it runs out of space. – false Aug 23 '21 at 20:18
  • @false Here is [tail recursive predicate](https://stackoverflow.com/a/68928151/2326961) that improves the time complexity of my [naive recursive predicate](https://stackoverflow.com/a/68877012/2326961) from exponential to polynomial time. I think they have the same space complexity O(*N*) though, since on [SWISH](https://swish.swi-prolog.org/) both programs exhaust resources for the Fibonacci of 34: `fib(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(0)))))))))))))))))))))))))))))))))), F).` raises `Stack limit (0.2Gb) exceeded`. Is it the improvement that you had in mind? – Géry Ogam Aug 27 '21 at 06:18

3 Answers3

4

This answer computes the fibonacci number "bottom up" using the two previous computed values, so that it will only make one recursive tail call:

fib(0, 0).
fib(s(0), s(0)).
fib(s(s(X)), F):-
  fib(X, 0, s(0), F, F).
  
fib(0, F_2, F_1, _, F):-
  sum(F_2, F_1, F).
fib(s(X), F_2, F_1, s(Y), F):-
  sum(F_2, F_1, F_0),
  fib(X, F_1, F_0, Y, F).

sum(0, Y, Y).
sum(s(X), Y, s(Z)):- 
  sum(X, Y, Z).

At least in SWI with default configuration it exhausts resources computing the fibonacci(37) building the addition term in sum/3.

gusbro
  • 22,357
  • 35
  • 46
  • 1
    Thanks Gustavo! However with your predicate `fib/2`, the query `fib(s(s(s(s(0)))), F).` returns `false` instead of `F = s(s(s(0))).`. – Géry Ogam Aug 25 '21 at 16:26
  • 1
    Both `fib(s(s(s(s(0)))),_)` (i.e. fib(4)) and `fib(s(s(s(s(s(0))))),_)` (fib(5)) fail... Replacing `s(s(Y))` by `s(Y)` in the second clause of `fib/5` should fix it... – jnmonette Aug 25 '21 at 17:33
  • 2
    Fixed that code. I was using `s(s(Y))` instead of `s(Y)` to fail faster when no solution will be found but it was making the procedure to fail for those (small) numbers. – gusbro Aug 26 '21 at 13:25
  • Thanks for the fix. On [SWISH](https://swish.swi-prolog.org/), your program (and my [naive recursive](https://stackoverflow.com/a/68877012/2326961) and [tail recursive](https://stackoverflow.com/a/68928151/2326961) ones) exhausts resources for the Fibonacci of 34: `fib(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(s(0)))))))))))))))))))))))))))))))))), F).` raises `Stack limit (0.2Gb) exceeded`. – Géry Ogam Aug 26 '21 at 16:06
  • 2
    The problem lies in `sum/3` building those huge numbers with successor arithmetics. Honestly you can't even make sense of those numbers if you don't convert them to decimal numbers. – gusbro Aug 26 '21 at 19:03
  • Thanks Gustavo, I am awarding you the bounty because you provided a tail recursive solution, alternative to [mine](https://stackoverflow.com/a/68928151/2326961). However I have not accepted it yet, because though I think our solutions are equivalent, I also find mine simpler. Do you agree? – Géry Ogam Aug 30 '21 at 20:18
  • 1
    They are quite similar and you should select the one that suits you better. I splitted mine that way so that the recursive procedure `fib/5` has both clauses with different principal functor (`0/0` and `s/1`) so any prolog processor should benefit of first argument indexing. Your answer has 2 clauses with the same principal functor `s/1` so the prolog processor should try to unify the first one (`s(0)`, and fail in all but the last recursion step). I don't think you can measure the difference in this example because most of the time will be spent on `sum/3` – gusbro Aug 30 '21 at 20:46
2

The failure slice causing universal non-termination when the first argument of fib/2 is unbound is

fib(s(s(N)), F) :-
  fib(N, F1),
  false,
  fib(s(N), F2),
  sum(F1, F2, F).

The reason is that only the first argument is restricted in the recursive call fib(N, F1), so if it is unbound the restriction does not apply.

cTI proves that

fib(A,B)terminates_if b(A).

To allow universal termination when the first argument is unbound, one should restrict the second argument in the recursive call and therefore the second argument should be bound for the restriction to apply:

fib(N, F) :-
  fib(N, F, F).

fib(0, 0, _).
fib(s(0), s(0), _).
fib(s(s(N)), F, s(X)) :-
  fib(N, F1, X),
  fib(s(N), F2, X),
  sum(F1, F2, F).

sum(0, N, N).
sum(s(N1), N2, s(S)) :-
  sum(N1, N2, S).

cTI proves that

fib(A,B)terminates_if b(A);b(B).

So now the query terminates:

?- fib(N, s(0)).
   N = s(0)
;  N = s(s(0))
;  false.
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
1

The naive recursive implementation of fib/2 provided in my other answer has a time complexity of O(φ^N) where φ is the golden ratio i.e. exponential time. Here is a tail recursive implementation of fib/2 which has a time complexity of O(N) i.e. linear time:

fib(N, F) :-
  fib(N, 0, s(0), F, F).

fib(0, A, _, A, _).
fib(s(0), _, B, B, _).
fib(s(s(N)), A, B, s(F), s(X)) :-
  sum(A, B, S),
  fib(s(N), B, S, s(F), X).

sum(0, N, N).
sum(s(N1), N2, s(S)) :-
  sum(N1, N2, S).

Sample queries

The most general query (all arguments are free):

?- fib(N, F).
   F = N, N = 0
;  F = N, N = s(0)
;  F = s(0), N = s(s(0))
;  F = s(s(0)), N = s(s(s(0)))
;  F = s(s(s(0))), N = s(s(s(s(0))))
;  F = N, N = s(s(s(s(s(0)))))
;  F = s(s(s(s(s(s(s(s(0)))))))), N = s(s(s(s(s(s(0))))))
;  F = s(s(s(s(s(s(s(s(s(s(s(s(s(0))))))))))))), N = s(s(s(s(s(s(s(0)))))))
;  …

Queries with a first argument that is free:

?- fib(N, 0).
   N = 0
;  false.

?- fib(N, s(0)).
   N = s(0)
;  N = s(s(0))
;  false.

?- fib(N, s(s(0)).
   N = s(s(s(0)))
;  false.

?- fib(N, s(s(s(0))).
   N = s(s(s(s(0))))
;  false.

?- fib(N, s(s(s(s(s(0)))))).
   N = s(s(s(s(s(0)))))
;  false.

?- fib(N, s(s(s(s(s(s(s(s(0))))))))).
   N = s(s(s(s(s(s(0))))))
;  false.

Queries with a second argument that is free:

?- fib(0, F).
   F = 0
;  false.

?- fib(s(0), F).
   F = s(0)
;  false.

?- fib(s(s(0)), F).
   F = s(0)
;  false.

?- fib(s(s(s(0))), F).
   F = s(s(0))
;  false.

?- fib(s(s(s(s(0)))), F).
   F = s(s(s(0)))
;  false.

?- fib(s(s(s(s(s(0))))), F).
   F = s(s(s(s(s(0)))))
;  false.
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
  • I don't think your algorithm has linear time complexity, as it has to make all those additions (which are not O(1) time using successor arithmetics) with the previous function values so it looks more like quadratic maybe?. – gusbro Aug 26 '21 at 19:07
  • @gusbro I think you are right, the tail recursive program may have a O(*N* ^2) time complexity if we don’t assume O(1) additions. Likewise for the naive recursive program, it may have a O(*N* × φ^ *N*) time complexity? – Géry Ogam Aug 26 '21 at 20:23
  • I really did not dig it through but you could probably deduct it "on paper". I actually think it is way worse than N^2 and my last asssertion of it being quadratic is a far lower bound. It also seems that space complexity for this algorithm with successor arithmetics would be about the same as its time complexity. – gusbro Aug 27 '21 at 00:05