5

The term fib(N,F) is true when F is the Nth Fibonacci number.

The following Prolog code is generally working for me:

:-use_module(library(clpfd)).
fib(0,0).
fib(1,1).
fib(N,F) :- 
  N #> 1, 
  N #=< F + 1, 
  F #>= N - 1,
  F #> 0, 
  N1 #= N - 1, 
  N2 #= N - 2, 
  F1 #=< F, 
  F2 #=< F,
  F #= F1 + F2, 
  fib(N1,F1),
  fib(N2,F2).

When executing this query (in SICStus Prolog), the first (and correct) match is found for N (rather instantly):

| ?- fib(X,377).
X = 14 ?

When proceeding (by entering ";") to see if there are any further matches (which is impossible by definition), it takes an enormous amount of time (compared to the first match) just to always reply with no:

| ?- fib(X,377).
X = 14 ? ;
no

Being rather new to Prolog, I tried to use the Cut-Operator (!) in various ways, but I cannot find a way to prevent the search after the first match. Is it even possible given the above rules? If yes, please let me know how :)

Martin Klinke
  • 7,294
  • 5
  • 42
  • 64
  • 4
    It is not recommended to use cut operator with clpfd. – joel76 Jan 19 '14 at 12:12
  • 1
    Is there a particular reason Why you are using CLPFD to compute one Fibonacci number? It's not a constraint problem. – lurker Jan 19 '14 at 12:17
  • 2
    @mbratch: it's a very good way to test declarative arithmetic on a simple problem. Indeed I haven't still found a solution to this question. – CapelliC Jan 19 '14 at 12:40
  • @CapelliC yes, good point. I see it's value as an exercise. In this case, it seems there's a "geometric explosion" in options as each recursive call deals with `F #= F1 + F2`. There are also a few redundant constraints in the code which could be removed (*e.g.* `N #=< F + 1` and `F #>= N - 1`). – lurker Jan 19 '14 at 12:43

3 Answers3

8

There are two parts to get what you want.

The first is to use call_semidet/1 which ensures that there is exactly one answer. See links for an implementation for SICStus, too. In the unlikely event of having more than one answer, call_semidet/1 produces a safe error. Note that once/1 and !/0 alone simply cut away whatever there has been.

However, you will not be very happy with call_semidet/1 alone. It essentially executes a goal twice. Once to see if there is no more than one answer, and only then again to obtain the first answer. So you will get your answer much later.

The other part is to speed up your definition such that above will not be too disturbing to you. The solution suggested by CapelliC changes the algorithm altogether which is specific to your concrete function but does not extend to any other function. But it also describes a different relation.

Essentially, you found the quintessential parts already, you only need to assemble them a bit differently to make them work. But, let's start with the basics.

CLPFD as CLP(Z)

What you are doing here is still not that common to many Prolog programmers. You use finite domain constraints for general integer arithmetics. That is, you are using CLPFD as a pure substitute to the moded expression evaluation found in (is)/2, (>)/2 and the like. So you want to extend the finite domain paradigm which assumes that we express everything within finite given intervals. In fact, it would be more appropriate to call this extension CLP(Z).

This extension does not work in every Prolog offering finite domains. In fact, there is only SICStus, SWI and YAP that correctly handle the case of infinite intervals. Other systems might fail or succeed when they rather should succeed or fail - mostly when integers are getting too large.

Understanding non-termination

The first issue is to understand the actual reason why your original program did not terminate. To this end, I will use a failure slice. That is, I add false goals into your program. The point being: if the resulting program does not terminate then also the original program does not terminate. So the minimal failure slice of your (presumed) original program is:

fiborig(0,0) :- false.
fiborig(1,1) :- false.
fiborig(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F #= F1+F2,
   fiborig(N1,F1), false,
   fiborig(N2,F2).

There are two sources for non-termination here: One is that for a given F there are infinitely many values for F1 and F2. That can be easily handled by observing that F1 #> 0, F2 #>= 0.

The other is more related to Prolog's execution mechanism. To illustrate it, I will add F2 #= 0. Again, because the resulting program does not terminate, also the original program will loop.

fiborig(0,0) :- false.
fiborig(1,1) :- false.
fiborig(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F #= F1+F2,
   F1 #> 0,
   F2 #>= 0,
   F2 #= 0,
   fiborig(N1,F1), false,
   fiborig(N2,F2).

So the actual problem is that the goal that might have 0 as result is executed too late. Simply exchange the two recursive goals. And add the redundant constraint F2 #=< F1 for efficiency.

fibmin(0,0).
fibmin(1,1).
fibmin(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F1 #> 0,
   F2 #>= 0,
   F1 #>= F2,
   F #= F1+F2,
   fibmin(N2,F2),
   fibmin(N1,F1).

On my lame laptop I got the following runtimes for fib(N, 377):

               SICStus                  SWI
             answer/total
fiborig:     0.090s/27.220s           1.332s/1071.380s
fibmin:      0.080s/ 0.110s           1.201s/   1.506s

Take the sum of both to get the runtime for call_semidet/1.

Note that SWI's implementation is written in Prolog only, whereas SICStus is partly in C, partly in Prolog. So when porting SWI's (actually @mat's) clpfd to SICStus, it might be comparable in speed.

There are still many things to optimize. Think of indexing, and the handling of the "counters", N, N1, N2.


Also your original program can be improved quite a bit. For example, you are unnecessarily posting the constraint F #>= N-1 three times!

Community
  • 1
  • 1
false
  • 10,264
  • 13
  • 101
  • 209
  • Wow, that's a very thorough explanation. Thank you very much for these unique insights. Just out of curiosity, where did you build these Prolog skills? Are you applying them in academia or commercially? I'm "only" doing a university course about logical (Prolog) and functional (Scheme/Racket) programming, so it was "only" an exercise. Again, thank you very much for the effort you put into this answer! – Martin Klinke Jan 22 '14 at 12:37
  • What is the type of algorithm that is the emergence of using CLP(FD) this way? Some reverse dynamic programming? When is this better for example to bisection algorithms? –  Mar 28 '16 at 17:31
  • Just a remark concerning the switching of the two last predicates. If `fibmin(N1,F1)` is before `fibmin(N2,F2)`, then a _different_ solution than `F1=2` for `N1=2` is looked for, which doesn't exist. Having switched those two predicates, searching for a different solution for `F=1` fails, because there must be exact one, so the program determines. Did I get this right? – Patrick Bucher Oct 15 '18 at 09:01
4

If you are only interested in the first solution or know that there is at most one solution, you can use once/1 to commit to that solution:

?- once(fib(X, 377)).

+1 for using CLP(FD) as a declarative alternative to lower-level arithmetic. Your version can be used in all directions, whereas a version based on primitive integer arithmetic cannot.

mat
  • 40,498
  • 3
  • 51
  • 78
  • Thank you for the pointer to once/1. It is very useful to know this option. However, I really appreciate @CapelliC's version that solves the problem with "pure" definitions. – Martin Klinke Jan 19 '14 at 18:03
  • 2
    Please keep in mind that @CappeliC solved a different problem than you asked: He gave you a faster algorithm for computing Fibonacci numbers. He did not answer your question on how to "prevent backtracking after first solution to Fibonacci pair". His version of the code is nice though (+1)! – mat Jan 20 '14 at 00:19
  • Changed accepted answer to this one as it indeed solves the actual problem that was asked for in the first place. – Martin Klinke Jan 21 '14 at 11:13
  • To make the predicate deterministic, look into predicates like `zcompare/3`, which is available for example in SWI-Prolog. It will make sense to have comparable functionality in SICStus as well. – mat Jan 21 '14 at 11:53
3

I played a bit with another definition, I wrote in standard arithmetic and translated to CLP(FD) on purpose for this question.

My plain Prolog definition was

fibo(1, 1,0).
fibo(2, 2,1).
fibo(N, F,A) :- N > 2, M is N -1, fibo(M, A,B), F is A+B.

Once translated, since it take too long in reverse mode (or doesn't terminate, don't know), I tried to add more constraints (and moving them around) to see where a 'backward' computation terminates:

fibo(1, 1,0).
fibo(2, 2,1).
fibo(N, F,A) :-
  N #> 2,
  M #= N -1,
  M #>= 0,  % added
  A #>= 0,  % added
  B #< A,   % added - this is key
  F #= A+B,
  fibo(M, A,B). % moved - this is key

After adding B #< A and moving the recursion at last call, now it works.

?- time(fibo(U,377,Y)).
% 77,005 inferences, 0.032 CPU in 0.033 seconds (99% CPU, 2371149 Lips)
U = 13,
Y = 233 ;
% 37,389 inferences, 0.023 CPU in 0.023 seconds (100% CPU, 1651757 Lips)
false.

edit To account for 0 based sequences, add a fact

fibo(0,0,_).

Maybe this explain the role of the last argument: it's an accumulator.

CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • In order to match my definition, I have added the rule: fib(N,F) :- fibo(N,_,F). Then it works the way I expect. Thank you very much! – Martin Klinke Jan 19 '14 at 18:00
  • Please note that @mat pointed out in the comment to his answer that your solution does not prevent the backtracking. Do you have any additional way of doing that or would once/1 be the way to go in the original sense of the question? – Martin Klinke Jan 20 '14 at 12:40
  • Sorry, no clue. Cuts don't play well with CLP(FD). They work at Prolog level, and are (I think) useless here. They serve if you know *at Prolog level* your commit points... of course, `fib(X,377),!.` works, as does once/1. – CapelliC Jan 20 '14 at 14:01
  • 2
    Your relation is different. `fib(0,F)` is not defined. – false Jan 21 '14 at 14:46