tddlog for Python¶
Python bindings for tddlog, built with PyO3 and maturin. tddlog computes exact marginal probabilities for programs in the Datalog fragment of ProbLog, by compiling them bottom up into tree decision diagrams.
Install¶
pip install tddlog
Wheels cover CPython 3.10 and later on Linux x86-64, macOS arm64 and Windows x86-64. Anywhere else, pip builds from the source distribution, which needs a Rust toolchain (1.88 or newer) and a C++20 compiler.
Use¶
import tddlog
solution = tddlog.solve("""
0.1::burglary.
0.2::earthquake.
0.7::hears_alarm(john).
0.4::hears_alarm(mary).
alarm :- burglary.
alarm :- earthquake.
calls(X) :- alarm, hears_alarm(X).
evidence(calls(john), true).
query(burglary).
query(earthquake).
query(calls(mary)).
""", evidence_probability=True)
for query, probability in solution.answers.items():
print(f"{query}: {probability:.6f}")
print(f"P(e) = {solution.evidence_probability:.6f}")
prints
burglary: 0.357143
earthquake: 0.714286
calls(mary): 0.400000
P(e) = 0.196000
solution.answers maps each query, as ProbLog prints it (\+p for
query(\+p)), to its probability, in the order of the program’s queries. It
is None where the evidence is inconsistent. examples/ holds this program
and one built from Python data.
Options¶
solve takes the command line’s options under its names:
vtree: the vtree strategy,"auto"by default, or one of the layoutstddlog --helplists ("td","closed","families","families:0","dynamic","program","program:fanin","decision"). Every strategy that applies to a program gives the same answers; they differ in how fast they get there.evidence_probability=True: also computeP(e), the probability of the evidence, assolution.evidence_probabilityand, exact below the least float,solution.log10_evidence_probability. Both areNoneotherwise. The evidence no query depends on is then compiled rather than dropped.queries=False: leave out the program’s queries and computeP(e)alone, as for a partition function. It needsevidence_probability=True.
An unknown strategy, or options that ask for nothing, raise ValueError.
Errors¶
A program tddlog cannot answer raises a subclass of tddlog.Error whose
message is the one the command line prints:
tddlog.ParseError: the program is not in the language tddlog reads;tddlog.GroundingError: it uses a construct outside the supported fragment or has a rule that is not range restricted;tddlog.CompilationError: the strategy asked for does not apply to the program, or the well-founded model of some world leaves an atom undefined (negation through a cycle with no two-valued reading).
The supported language is described in the guide.
Threads¶
The interpreter lock is released for the whole of a solve, so a caller can
solve several programs at once from Python threads. Each solve runs on the
thread that calls it.
Building from source¶
The extension builds the tddlog sources in the repository, so it needs a Rust
toolchain, a C++20 compiler for the tree-decomposition solvers goatd vendors,
and maturin. The three notice files are copied in from the repository root
first, since pyproject.toml cannot reference files above its own directory:
mkdir -p bindings/python/notices
cp LICENSE NOTICE THIRD-PARTY.md bindings/python/notices/
pip install maturin
maturin build --release --manifest-path bindings/python/Cargo.toml
maturin develop --release installs the extension into the active
virtualenv, and pytest bindings/python/tests tests it.
Examples¶
examples/alarm.py is the program above; its docstring works the answers
out by hand:
"""The alarm example of Fierens et al. (2015): the probabilities of three
queries given that John calls, and the probability that he calls.
P(alarm) = 1 - 0.9 * 0.8 = 0.28 and P(calls(john)) = 0.28 * 0.7 = 0.196, so
P(burglary | calls(john)) = 0.1 * 0.7 / 0.196 = 0.357143. Run it with
python examples/alarm.py
to print
burglary: 0.357143
earthquake: 0.714286
calls(mary): 0.400000
P(e) = 0.196000 (log10 -0.707744)
"""
import tddlog
PROGRAM = """
0.1::burglary.
0.2::earthquake.
0.7::hears_alarm(john).
0.4::hears_alarm(mary).
alarm :- burglary.
alarm :- earthquake.
calls(X) :- alarm, hears_alarm(X).
evidence(calls(john), true).
query(burglary).
query(earthquake).
query(calls(mary)).
"""
solution = tddlog.solve(PROGRAM, evidence_probability=True)
for query, probability in solution.answers.items():
print(f"{query}: {probability:.6f}")
print(
f"P(e) = {solution.evidence_probability:.6f}"
f" (log10 {solution.log10_evidence_probability:.6f})"
)
examples/graph.py builds a program from Python data:
"""A program built from Python data: the probability that a path joins two
nodes of a graph whose edges are present independently, ProbLog's system
test 7. ProbLog answers path(1,5) 0.25824 and path(1,6) 0.2167296.
Run it with
python examples/graph.py
to print
path(1,5): 0.258240
path(1,6): 0.216730
"""
import tddlog
EDGES = {
(1, 2): 0.6,
(1, 3): 0.1,
(2, 5): 0.4,
(2, 6): 0.3,
(3, 4): 0.3,
(4, 5): 0.8,
(5, 6): 0.2,
}
QUERIES = [(1, 5), (1, 6)]
facts = [f"{p}::edge({a},{b})." for (a, b), p in EDGES.items()]
rules = [
"path(X,Y) :- edge(X,Y).",
"path(X,Y) :- edge(X,Z), path(Z,Y).",
]
queries = [f"query(path({a},{b}))." for a, b in QUERIES]
program = "\n".join(facts + rules + queries)
solution = tddlog.solve(program)
for query, probability in solution.answers.items():
print(f"{query}: {probability:.6f}")
The guide describes the language tddlog reads, with worked examples that open in the playground.
Reference¶
- tddlog.solve(program, *, vtree='auto', evidence_probability=False, queries=True)¶
Parse, ground, compile and count
program, a ProbLog program as text, and return the answers to its queries.vtreeis the command line’s--vtree:"auto", the default, or one of the layouts it lists.evidence_probability=Truealso computesP(e), compiling the evidence no query depends on rather than dropping it.queries=Falseleaves out the program’s queries, as--no-queriesdoes, and needsevidence_probability=True.An unknown
vtree, or options that ask for nothing, raiseValueError; a program tddlog cannot answer raises a subclass oftddlog.Error. The interpreter lock is released for the whole run.
- class tddlog.Solution¶
The answers to a program’s queries, and the probability of its evidence where
solvewas asked for it.- answers¶
A dict from each query, as ProbLog prints it (
\+before a negative one), to its probability, in the program’s order;Nonewhere the evidence is inconsistent.
- evidence_probability¶
P(e), the probability of the evidence: 1.0 with none, 0.0 where it is inconsistent, and 0.0 too where it is under the least float, whichlog10_evidence_probabilitystill gives.Noneunlesssolvewas called withevidence_probability=True.
- log10_evidence_probability¶
The base-10 logarithm of
P(e), exact beyond a float’s range:-infwhere the evidence is inconsistent.Noneunlesssolvewas called withevidence_probability=True.
- exception tddlog.Error¶
A program tddlog could not answer: the base of ParseError, GroundingError and CompilationError.
- exception tddlog.ParseError¶
The program is not in the language tddlog reads.
- exception tddlog.GroundingError¶
The program parses but cannot be ground: an unsupported construct or a rule that is not range restricted.
- exception tddlog.CompilationError¶
The ground program could not be compiled and counted under the vtree strategy asked for, or the well-founded model of some world leaves an atom undefined.