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 string
  • int: an integer number
  • float: a floating point number
  • list: a list of terms
  • term: 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.