The term fib(N,F)
is true when F
is the N
th 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 :)
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.
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.
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With