Binomial coefficient in Prolog
I tried to implement the binomial coefficient in Prolog, but discovered that my naive implementation wasn't fast enough for the problem that I had. Instead of worrying about optimizing my implementation I decided to call into C instead. Prolog is already dependent on the GNU MP bignum library, but it does not export the binomial coefficient function that is already present in GMP.
Calling C from Prolog
I am using SWI-Prolog and we need to include the header files from
SWI-Prolog so we can build a dynamic library that Prolog can
call. We also include GMP so we can get access to the mpz_bin_ui
function that calculates the binomial coefficient.
1: #include <SWI-Prolog.h> 2: #include <gmp.h>
Defining predicates in C is pretty similar to defining them in Prolog. In this case I define a predicate that takes three terms as arguments: The size of the set to choose from, the number of distinct elements to draw from the set, and the resulting binomial coefficient.
In contrast to defining predicates in Prolog, it is also neccessary to state what the types of the function and arguments are. The function also has a return type. The return from the function is there to signal to Prolog if the predicate succeeded in unifying the terms or not.
3: static foreign_t 4: pl_choose(term_t size, term_t take, term_t result) 5: { 6: mpz_t rop; 7: mpz_t n; 8: unsigned long int k; 9: int rc; 10: 11: mpz_init(rop); 12: mpz_init(n); 13: 14: if ( PL_get_mpz(take, rop) && PL_get_mpz(size, n) ) 15: { 16: k = mpz_get_ui(rop); 17: mpz_bin_ui(rop, n, k); 18: rc = PL_unify_mpz(result, rop); 19: } 20: else 21: { 22: rc = FALSE; 23: } 24: 25: mpz_clear(rop); 26: mpz_clear(n); 27: return rc; 28: }
At line 3 the return type of the predicate is defined. We see
that it is a static function and that it will return the type
foreign_t
. All predicates implemented in C needs to have the
return type as foreign_t
.
We can see on line 4 that the predicate has 3 arguments. These arguments function mostly in the same way that they do in Prolog. In this predicate it is only the last argument that is allowed to be a (Prolog) variable. The other two are needed to calculate the binomial coefficient.
From line 6 the variables that we need are defined. mpz_t
is
the type of numbers in the GMP library.
The mpz_t
variables needs to be initialized in memory and we do
that on line 11.
In the if statement on line 14 we try to convert the values in the
Prolog terms into mpz values. If any of the input variables are not
a number, the PL_get_mpz
function will return a false value and
the if statement will fail.
Since the binomial coefficient function takes a unsigned int
as
the 3rd argument we need to convert the mpz value from the input to
a unsigned int
. That is done on line 16.
mpz_bin_ui
is the function that calculates the binomial
coefficient. It takes two mpz_t
values and an unsigned int
. You
might notice that we are using the same variable that we used for
converting one of the term_t
to an mpz value. This is perfectly
fine as we converted that number and stored it in the k
variable. We could clear the variable first, but it is not needed as
mpz_bin_ui
will just overwrite the value stored in the variable.
mpz_bin_ui
stores the result of the calculation in the first
argument. The second argument is the size of the set and the third
is the number of elements to draw from th set.
After calculating the binomial coefficient we unify the value stored
in rop
with the result
variable. We do that with PL_unify_mpz
on line 18. rc
, which is the return value for the predicate, is
set to the result of unifying the two variables; either true or
false.
If the function, for some reason, didn't recieve the correct input it will instead of trying to calculate the binomial coefficient just set the return value of the predicate to false (line 22).
Before we exit the predicate we need to clear the mpz variables as otherwise we will leak memory (line 25).
In the end we return rc
so that we can tell Prolog if we succeeded
in calculating the binomial coefficient or not.
29: install_t 30: install_choose() 31: { 32: PL_register_foreign("choose", 3, pl_choose, 0); 33: }
Prolog also needs to be told which functions in the file are
predicates (line 30). We do this by creating a function that
returns install_t
and has the name install_*
, where the *
is
the name of the file it is in. In our case it is "choose
". We also
need to call PL_register_foreign
on every function that we want to
export to Prolog. PL_register_foreign
takes 4 arguments. The name
of the function as it will appear in Prolog, the number of
arguments, the C-function to call and, some flags. You can learn
more about the flags in the SWI-Prolog documentation.
Building the library
Building the C-code has been made pretty easy1. We use the swipl-ld
command that is packaged with SWI-Prolog. On my OS X box I need to
add gmp as a library that we depend on. The -shared
argument
ensures that we are building a dynamic library. We need it to be a
dynamic library if Prolog is going to be able to call it. -o
sets
what to call the compiled library. Depending on if you are on OS X,
Linux or, Windows the extension will be .dylib
, .so
or .dll
.
swipl-ld -lgmp -shared -o choose choose.c
Calling the library from Prolog
Calling the newly built library from Prolog is quite easy. All you
have to do is call :- use_foreign_library
with the the name of the
library you want to load as an argument. I my case I also defined a
module to export the foreign code from.
:- module(binomial, [choose/3]). :- use_foreign_library(choose).
Source code
You can see uninterrupted source at:
Footnotes:
There is however a problem right now with the prolog package on
Ubuntu. It will compile and link fine with swipl-ld
, but when you
try to call the function from Prolog it won't register.