Writing models in ProbLog: advanced concepts¶
Output and errors your ProbLog program¶
ProbLog does not support I/O as Prolog does. It is however possible to write out some information using the following predicates:
debugprint/N
: takes up to 10 arguments which are printed followed by a new linewrite/N
: takes up to 10 arguments which are printed out; removes quotes; no new linenl/0
: writes a new line characterwritenl/N
: same aswrite\N
followed bynl
.error/N
: raise a UserError based with a message composed of up to 10 arguments
Calling Python from ProbLog¶
ProbLog allows calling functions written in Python from a ProbLog model.
The functionality is provided by the problog.extern
module.
This module introduces two decorators.
problog_export
: for deterministic functions (i.e. that return exactly one result)problog_export_nondet
: for non-deterministic functions (i.e. that return any number of results)problog_export_raw
: for functions without clear distinction between input and output
These decorators take as arguments the types of the arguments. The possible argument types are
str
: a stringint
: an integer numberfloat
: a floating point numberlist
: a list of termsterm
: an arbitrary Prolog term
Each argument is prepended with a +
or a -
to indicate whether it is an input or an output argument.
The arguments should be in order with input arguments first, followed by output arguments.
The function decorated with these decorators should have exactly the number of input arguments and it should return a tuple of length the number of output arguments. If there is only one output argument, it should not be wrapped in a tuple.
Functions decorated with problog_export_nondet
should return a list of result tuples.
Functions decorated with problog_export_raw
should return a list of tuples where each tuple
contains a value for each argument listed in the specification.
A function decorated with this decorator should have only +
specifiers and it should be prepared to receive
additional arguments containing the execution context (i.e. by adding **kwargs as last argument).
The internal Prolog database that is in use can be accessed through the variable problog_export.database
.
For example, consider the following Python module numbers.py
which defines two functions.
from problog.extern import problog_export, problog_export_nondet, problog_export_raw
@problog_export('+int', '+int', '-int')
def sum(a, b):
"""Computes the sum of two numbers."""
return a + b
@problog_export('+int', '+int', '-int', '-int')
def sum_and_product(a, b):
"""Computes the sum and product of two numbers."""
return a + b, a * b
@problog_export_nondet('+int', '+int', '-int')
def in_range(a, b):
"""Returns all numbers between a and b (not including b)."""
return list(range(a, b)) # list can be empty
@problog_export_nondet('+int')
def is_positive(a):
"""Checks whether the number is positive."""
if a > 0:
return [()] # one result (empty tuple)
else:
return [] # no results
@problog_export_raw('+term', '+term')
def successor(a, b, **kwargs):
"""Defines the successor relation between a and b."""
from problog.engine_builtin import check_mode
# We support three modes: a,b both integer; a integer and b variable; a variable and b integer.
# This will raise an error for any other case.
mode = check_mode([a, b], ['ii', 'iv', 'vi'], functor='successor', **kwargs)
if mode == 0:
# Both integers
av = int(a)
bv = int(b)
if av + 1 == bv:
return [(av, bv)]
else:
return []
elif mode == 1:
# Integer / Variable
av = int(a)
return [(av, Constant(av + 1))]
else:
# Variable / Integer
bv = int(b)
return [(Constant(bv - 1), bv)]
This module can be used in ProbLog by loading it using the use_module
directive.
:- use_module('numbers.py').
query(sum(2,4,X)).
query(sum_and_product(2,3,X,Y)).
query(in_range(1,4,X)).
query(is_positive(3)).
query(is_positive(-3)).
The result of this model is
in_range(1,4,1): 1
in_range(1,4,2): 1
in_range(1,4,3): 1
sum(2,4,6): 1
sum(2,3,5,6): 1
is_positive(-3): 0
is_positive(3): 1
It is possible to store persistent information in the internal database.
This database can be accessed as problog_export.database
.
Using data from an SQLite database¶
ProbLog provides a library that offers a very simple interface to an SQLite database.
Assume we have an SQLite database friends.db
with two tables:
- person(name)
- A list of persons.
- friend_of(name1, name2, probability)
- A list of friendship relations.
We can load this database into ProbLog using the library db
and the predicate sqlite_load(+Filename)
.
:- use_module(library(db)).
:- sqlite_load('friends.db').
This will create a predicate for each table in the database with as arity the number of columns of that table. We can thus write the following variation of the smokers examples:
:- use_module(library(sqlite)).
:- sqlite_load('friends.db').
P :: influences(X, Y) :- friend_of(X, Y, P).
0.3::smokes(X) :- person(X). % stress
smokes(X) :- influences(Y, X), smokes(Y).
The library will automatically translate a call to a database predicate into a query on the database, for example, the call friend_of(ann, B, P)
will be translated to the query
SELECT name1, name2, probability FROM friend_of WHERE name1 = 'ann'
Using data from a CSV file¶
ProbLog provides a library that offers a simple interface to an CSV file.
Assume we have two CSV files person.csv
and friend_of.csv
containing data for two predicates:
- person(name)
- A list of persons.
- friend_of(name1, name2, probability)
- A list of friendship relations.
These file contain as columns the terms of the predicate and the first line are the column names.
$ cat person.csv
"name"
"ann"
"bob"
$ cat friend_of.csv
"p1","p2","prob"
"ann","bob",0.2
We can load these files into ProbLog using the library db
and the predicate csv_load(+Filename, +Predicatename)
.
:- use_module(library(db)).
:- csv_load('person.csv', 'person').
:- csv_load('friend_of.csv', 'friend_of').
This will create a two predicates, one for each file with as arity the number of columns. We can thus write the following variation of the smokers examples:
:- use_module(library(db)).
:- csv_load('person.csv', 'person').
:- csv_load('friend_of.csv', 'friend_of').
P :: influences(X, Y) :- friend_of(X, Y, P).
0.3::smokes(X) :- person(X). % stress
smokes(X) :- influences(Y, X), smokes(Y).
The library will automatically translate a call to predicates person
and friends_of
into a query on the respective csv-file. For example, the call friend_of(ann, B, P)
will be matched to all lines that match
"ann",*,*
Using continuous distributions (sampling only)¶
When using the sampling mode from Python, you can add arbitrary distributions with specialized sampling algorithms. This can be achieved by passing them to the sample function.
from problog.tasks import sample
from problog.program import PrologString
modeltext = """
my_uniform(0,10)::a.
0.5::b.
c :- value(a, A), A >= 3; b.
query(a).
query(b).
query(c).
"""
import random
import math
# Define a function that generates a sample.
def integer_uniform(a, b):
return math.floor(random.uniform(a, b))
model = PrologString(modeltext)
# Pass the mapping between name and function using the distributions parameter.
result = sample.sample(model, n=3, format='dict', distributions={'my_uniform': integer_uniform})
Example output: [{a: 0.0, b: True, c: True}, {a: 7.0, b: False, c: True}, {a: 0.0, b: False, c: False}]