CLP(FD)-ying Simultaneous Recursion for Fibonacci Lukas Numbers Possible?
Asked Answered
S

1

9

There are some instances where recursive predicates can be CLP(FD)-fied with the benefit that the predicate turns bidirectional. What are the limits of this method? For example can the following computation CLP(FD)-fied:

Fn: n-th Fibonacci Number
Ln: n-th Lucas Number (starting with 2)

By this doubling recursion step:

F2n = Fn*Ln
L2n = (5*Fn^2+Ln^2)//2

And this incrementing recursion step:

Fn+1 = (Fn+Ln)//2
Ln+1 = (5*Fn+Ln)//2

The traditional Prolog realization works already from n to Fn. Can this be turned into a CLP(FD) program preserving the fast recursion and at the same time making it bidirectionally, for example figuring out the index n for Fn=377? If yes how? If not why?

Bye

Scutcheon answered 28/3, 2016 at 14:29 Comment(4)
fibluc/3 is ca. 20% faster than fib/2. Could have to do that fib/2 is using call/n and the text book like programming stye. See comment in your answer. But I guess one will run into the same CLP(FD)-ying problems for both algorithms.Scutcheon
Now that I get a better look, it seems this is the same algorithm in different clothes. I will delete my answer.Persson
@Boris I think your fib/2 was indeed different, it is a well known algorithm for Fibonacci Niumbers which doesn't generate Lucas Numbers on the way. See also here: en.wikipedia.org/wiki/Fibonacci_number#Matrix_form But it was similar by using doubling and incrementing I guess.Scutcheon
Yes, indeed, this is what I meantPersson
S
3

Yes, it can be done by constraining the values. You can also move the recursion to be tail recursion, although it's not required to get the solutions:

fibluc(0, 0, 2).
fibluc(1, 1, 1).
fibluc(N, F, L) :-
    N in 2..1000,        % Pick a reasonable value here for 1000
    [F, L] ins 1..sup,
    N rem 2 #= 1,
    M #= N-1,
    F #= (F1 + L1) // 2,
    L #= (5*F1 + L1) // 2,
    fibluc(M, F1, L1).
fibluc(N, F, L) :-
    N in 2..1000,        % Pick a reasonable value here for 1000
    [F, L] ins 1..sup,
    N rem 2 #= 0,
    M #= N // 2,
    F #= F1 * L1,
    L #= (5*F1*F1 + L1*L1) // 2,
    fibluc(M, F1, L1).

Will yield:

?- fibluc(10, X, Y).
X = 55,
Y = 123 ;
false.

?- fibluc(N, 55, Y).
N = 10,
Y = 123 ;
false.

?- fibluc(N, X, 123).
N = 10,
X = 55 ;
false.

?- fibluc(N, 55, 123).
N = 10 ;
false.

?- fibluc(N, 55, 125).
false.

?- fibluc(N, X, Y).
N = X, X = 0,
Y = 2 ;
N = X, X = Y, Y = 1 ;
N = 3,
X = 2,
Y = 4 ;
N = 7,
X = 13,
Y = 29 ;
N = 15,
X = 610,
Y = 1364 ;
N = 31,
X = 1346269,
Y = 3010349 ;
N = 63,
X = 6557470319842,
Y = 14662949395604 ;
...

This could be modified to generate results for increasing values of N when N is uninstantiated.
Here's a timed, compound query example, run in SWI Prolog 7.1.33 under Linux:

?- time((fibluc(100, X, Y), fibluc(N, X, Z))).
% 11,337,988 inferences, 3.092 CPU in 3.100 seconds (100% CPU, 3666357 Lips)
X = 354224848179261915075,
Y = Z, Z = 792070839848372253127,
N = 100 ;
% 1,593,620 inferences, 0.466 CPU in 0.468 seconds (100% CPU, 3417800 Lips)
false.

?-

Using SWI Prolog 7.2.3 with the same code above and the same compound query, the code does go off for a very long time. I waited at least 15 minutes without termination. It's still running right now... I may check on it in the morning. :)

I did, however, re-arrange the above code to move the recursive call back to where the original code had it as follows:

fibluc(0, 0, 2).
fibluc(1, 1, 1).
fibluc(N, F, L) :-
    N in 2..1000,        % Pick a reasonable value here for 1000
    [F, L] ins 1..sup,
    N rem 2 #= 1,
    M #= N-1,
    fibluc(M, F1, L1),
    F #= (F1 + L1) // 2,
    L #= (5*F1 + L1) // 2.
fibluc(N, F, L) :-
    N in 2..1000,        % Pick a reasonable value here for 1000
    [F, L] ins 1..sup,
    N rem 2 #= 0,
    M #= N // 2,
    fibluc(M, F1, L1),
    F #= F1 * L1,
    L #= (5*F1*F1 + L1*L1) // 2.

In this case, the favorable results returned:

?- time((fibluc(100, X, Y), fibluc(N, X, Z))).
% 10,070,701 inferences, 3.216 CPU in 3.222 seconds (100% CPU, 3131849 Lips)
X = 354224848179261915075,
Y = Z, Z = 792070839848372253127,
N = 100 ;
% 1,415,320 inferences, 0.493 CPU in 0.496 seconds (100% CPU, 2868423 Lips)
false.

Note that the performance of CLP(FD) can be vastly different between different Prolog interpreters. It's interesting that, with SWI Prolog, the ability to handle the tail recursive case was temporarily there with version 7.1.33.

Shellback answered 28/3, 2016 at 15:27 Comment(10)
@j4nbur53 which Prolog interpreter are you using? In SWI Prolog, the query, ?- fibluc(100, X, Y), fibluc(N, X, Z). returns a solution within a few seconds (see my updated answer). Poor performance can be due to the interpreter's inefficient implementation of CLP(FD). sup is quite large. I'm not sure what the largest N would be before results overflow. Although the difference in performance you are seeing is consistent consistent with my experience with similar problems. The constraints on F and L probably contribute to that.Shellback
In earlier SWI versions, you can use (/)/2 instead of (//)/2 to denote floored division in CLP(FD) expressions. The different speeds you see for different arguments are likely due to different propagations resulting from algebraic properties like primality, divisibility etc. of the arising integers and domain boundaries, which sometimes help to speed up propagation a lot. In SWI-Prolog, sup means actual infinity. You cannot exceed that boundary, only run out of memory if the numbers get too large. This depends on the maximum size of the global stack and the available RAM.Flyblown
I was addressing an implicit question by lurker, not you.Flyblown
I was addressing questions by both you and lurker .... in the same comment! Nobody expects the Spanish addressing-two-people-in-the-same-comment-commenter!Flyblown
@j4nbur53 my version of SWI Prolog is 7.1.33. I'll install 7.2.3 and see if I get the same results you do.Shellback
@j4nbur53 my version of SWI Prolog is 7.1.33. I downloaded and compiled the 7.2.3 sources for Linux (which is my platform) and ran the same test on 7.2.3 with similar results that you observed. However, if I move the recursive fibluc call back to where you had it, I get back to favorable results.Shellback
Since the non inverted fibluc/3 is time O(log n), a simple bisection would give time O((log n)^2) I guess. I have the feeling the CLP(FD) translation of fibluc/3 only cannot reproduce such a favorable behaviour. I don't know yet what time O(..) is to expect here, but it looks exponential to me.Scutcheon
@j4nbur53 right. That would be a good basis for a new question I think regarding the performance of CLP(FD) in this or similar problems. I believe, though, that I did answer the scope of your original question, which was Can this be turned into a CLP(FD) program?.Shellback
I wouldn't says so that it answers the question completely. I think its still open, since I was asking "preserving the fast recursion". So of course the question is about turning it into CLP(FD) and preserving speed. I have an idea of a different translation which uses reification, which could be more speedier. For the branching we could use (# \ / )/2, but did not yet have time to look at it.Scutcheon
The get a glimpse what I have in mind, see for example: https://mcmap.net/q/266378/-prolog-union-for-a-u-b-u-cScutcheon

© 2022 - 2024 — McMap. All rights reserved.