Higher-order functions / Meta-predicates

Higher-order functions can be used to construct and manipulate probability distributions. This includes passing predicates (or functions) as variables and meta-predicates that apply a predicate on all members of a list (or higher-order functions) such as map, partition, fold, and scan.

We will show to use meta-predicates based on examples taken from the book Probabilistic Models of Cognition by Noah D. Goodman and Josh B. Tenenbaum.

ProbLog uses memoization by default. In contrast with languages such as Church, ProbLog does not require an anonymous function to represent a new coin but a unique identifier to be added as a term. In the following we define three coins c1, c2, and c3. The first term in coin/3, fair_coin/2, trick_coin/2 and bent_coin/2 denotes the coin.

PH::coin(C,h,PH); PT::coin(C,t,PH) :- PT is 1.0-PH. fair_coin(C,S) :- coin(C,S,0.5). trick_coin(C,S) :- coin(C,S,0.95). bent_coin(C,S) :- coin(C,S,0.25). query(fair_coin(c1,_)). query(trick_coin(c2,_)). query(bent_coin(c3,_)).

When you want to bend an already defined coin, you can make use of higher-order functions in ProbLog. The following code defines a fair coin as above and then applies a bend function to that coin to “bend it”. When passing a predicate (or function) as a variable, it can be called using the call predicate. Also notice that in ProbLog an if-then-else construct is expressed using pattern matching.

PH::coin(C,PH,h); PT::coin(C,PH,t) :- PT is 1.0-PH. fair_coin(C, R) :- coin(C, 0.5, R). bend(Cointype, C, R) :- call(Cointype, C, R2), (R2=h, coin(C,0.7,R); R2=t, coin(C,0.1,R)). bent_coin(C, R) :- bend(fair_coin, C, R). query(bent_coin(c1, _)).

ProbLog offers the lists and apply libraries to work with lists and apply goals (or functions) over a list of elements. This includes operations such as member, maplist, foldl or scanl (based on the SWI Prolog lists and apply libraries).

For example, to compute the expected number of heads we expect to see when we toss a weighted coin 10 times we can use a maplist and sum_list call (notice that this will be very slow and return a time out in the online interface, see the next example for an improved version).

:- use_module(library(apply)). :- use_module(library(lists)). PH::make_coin(C,PH). coin(C) :- make_coin(C,0.8). tonum(C, Num) :- (coin(C), Num=1; \+coin(C), Num=0). total(S) :- findall(X, between(1,10,X), L), maplist(tonum, L, Nums), sum_list(Nums, S). query(total(_)).

To find all possible traces, ProbLog makes use of tabling of atoms. In the example above we pass on the full list (1 to 10) and iterate over all possible versions where each element is replaced by either 0 or 1. Finally, the sum over the new list is computed. This requires a large number of instantiations to be tabled. You can observe the exhaustive enumeration by running the program above in your terminal after appending debugprint(Nums), to the end of line 9 (the online editor will time out before it prints the results).

A better approach is to use foldl. Folding explicitly computes an intermediate result after applying the given predicate to a list member and ProbLog will table the intermediate results which is much faster.

:- use_module(library(apply)). :- use_module(library(lists)). PH::make_coin(C,PH). coin(C) :- make_coin(C,0.8). cumsum(C, In, Out) :- (coin(C), Out is In+1; \+coin(C), Out=In). total(S) :- findall(X, between(1,10,X), L), foldl(cumsum, L, 0, S). query(total(_)).

If you want to write the above program without higher-order functions you can use a recursive predicate that directly implements the same operation as the foldl and cumsum operation above:

PH::make_coin(C,PH). coin(C) :- make_coin(C,0.8). coins_r(SC,SC,0). coins_r(SC,S,CNT) :- CNT > 0, (coin(CNT), SC2 is SC+1; \+coin(CNT), SC2 is SC), CNT2 is CNT-1, coins_r(SC2,S,CNT2). total(S) :- coins_r(0,S,10). query(total(_)).