tddlog for C¶
A C ABI over tddlog: a ProbLog program goes in as text, with the command line’s options, and the probabilities of its queries come out through an opaque solution handle.
The ABI is unstable before 1.0. Struct layouts, status codes and
signatures can change in any release. Build against the version you ship with
and check tddlog_version() at run time.
This is a separate crate with its own Cargo.toml. It builds the tddlog
sources in the repository and carries the same version, which
tddlog_version() reports.
Use the API¶
TddlogOptions options = tddlog_options_default();
options.evidence_probability = true;
TddlogSolution *solution;
if (tddlog_solve(program, &options, &solution) != TDDLOG_OK) {
fprintf(stderr, "%s\n", tddlog_last_error_message());
return 1;
}
for (size_t i = 0; i < tddlog_solution_num_answers(solution); i++)
printf("%s:\t%f\n", tddlog_solution_query(solution, i),
tddlog_solution_probability(solution, i));
double evidence, log10_evidence;
if (tddlog_solution_evidence_probability(solution, &evidence, &log10_evidence))
printf("P(e):\t%g\tlog10 %f\n", evidence, log10_evidence);
tddlog_solution_free(solution);
examples/alarm.c is this program on the alarm example, checked against the
probabilities worked out by hand; tests/answers.c checks a dozen of
ProbLog’s system tests against ProbLog’s answers under several vtree
strategies.
TddlogOptionsholds the command line’s options:vtree(--vtree, null for"auto"),evidence_probability(--evidence-probability) andqueries(false for--no-queries, which needsevidence_probability).tddlog_solvetakes null options as the defaults.tddlog_solvereturnsTDDLOG_OKor an error status:TDDLOG_ERROR_INVALID_INPUTfor a null pointer, text that is not UTF-8, an unknown strategy or options that ask for nothing;TDDLOG_ERROR_PARSE,TDDLOG_ERROR_GROUNDandTDDLOG_ERROR_COMPILEfor a program tddlog cannot answer;TDDLOG_ERROR_PANICfor a bug.tddlog_last_error_message()gives the message the command line prints.The answers are in the order of the program’s queries, each query written as ProbLog prints it (
\+pforquery(\+p)). Where the evidence is inconsistent,tddlog_solution_consistentis false and there are none.tddlog_solution_evidence_probabilitygivesP(e)and its base-10 logarithm, which stays exact whereP(e)is under the least double, whereevidence_probabilityasked for it, and returns false otherwise.
The caller owns the solution and releases it with tddlog_solution_free. The
query strings belong to the solution and live until it is freed;
tddlog_version() is static; the string from tddlog_last_error_message()
belongs to tddlog and lives until the next tddlog_solve on that thread.
tddlog is single-threaded. Each thread may call tddlog_solve on its own
program; error messages are recorded per thread, and threads may read one
solution at once.
Build and install with CMake¶
From a source checkout, with Rust (1.88 or newer), a C++20 compiler for the tree-decomposition solvers goatd vendors, a C compiler and CMake:
cmake -S bindings/c -B build/c -DCMAKE_BUILD_TYPE=Release
cmake --build build/c --config Release
ctest --test-dir build/c -C Release --output-on-failure
cmake --install build/c --config Release --prefix /your/install/prefix
The installation holds the header, the shared library, a CMake package and a
pkg-config file, and LICENSE, NOTICE and THIRD-PARTY.md under
share/licenses/tddlog. An application then uses
find_package(tddlog CONFIG REQUIRED) and links tddlog::tddlog, with
CMAKE_PREFIX_PATH set to the prefix, or reads its flags from pkg-config:
export PKG_CONFIG_PATH=/your/install/prefix/lib/pkgconfig
cc prog.c $(pkg-config --cflags --libs tddlog) -o prog
The library needs no Rust toolchain at run time; the dynamic loader has to
find it (an rpath, LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on
macOS).
python bindings/c/check.py does all of the above and then builds and runs
the example against the installed package, moved to another prefix, through
both CMake and pkg-config, which it needs on PATH.
Build with cargo alone¶
cargo build --release --manifest-path bindings/c/Cargo.toml
builds libtddlog_c.so (libtddlog_c.dylib on macOS) and libtddlog_c.a in
bindings/c/target/release; the header is bindings/c/include/tddlog.h.
Against the shared library nothing else is needed. A static link also needs
the C++ runtime the tree-decomposition solvers are compiled against and the
system libraries Rust’s standard library uses; rustc prints the list:
cargo rustc --release --manifest-path bindings/c/Cargo.toml -- \
--print native-static-libs
which on Linux prints something like -lstdc++ -lgcc_s -lutil -lrt -lpthread -lm -ldl -lc. Pass that after the archive:
cc -Ibindings/c/include prog.c bindings/c/target/release/libtddlog_c.a \
-lstdc++ -lgcc_s -lutil -lrt -lpthread -lm -ldl -lc -o prog
Install from a release¶
Each GitHub release attaches a tddlog-c-<tag>-<target>.tar.gz archive for
x86_64-unknown-linux-gnu and aarch64-apple-darwin, and a
tddlog-c-<tag>-x86_64-pc-windows-msvc.zip. It holds the header,
the shared and the static library, LICENSE, NOTICE, THIRD-PARTY.md, and
NATIVE-STATIC-LIBS.txt, the system libraries a static link needs on that
platform. On Windows the libraries are tddlog_c.dll, its import library
tddlog_c.dll.lib and the static tddlog_c.lib; the CMake and pkg-config
packages are not built there.
Regenerate the header¶
include/tddlog.h is generated from src/lib.rs by
cbindgen, which the crate’s build
script runs, and committed. check.py fails where the committed copy has
drifted; after changing src/lib.rs, run
python bindings/c/check.py --write-header
The example¶
examples/alarm.c, the program above on the alarm example, checked
against the probabilities worked 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. The program prints the
* answers as the command line does, checks them against these numbers, and
* prints
*
* burglary: 0.357143
* earthquake: 0.714286
* calls(mary): 0.400000
* P(e): 0.196000 log10 -0.707744
*
* Building and linking it is covered in bindings/c/README.md. */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tddlog.h"
static const char PROGRAM[] =
"0.1::burglary.\n"
"0.2::earthquake.\n"
"0.7::hears_alarm(john).\n"
"0.4::hears_alarm(mary).\n"
"alarm :- burglary.\n"
"alarm :- earthquake.\n"
"calls(X) :- alarm, hears_alarm(X).\n"
"evidence(calls(john), true).\n"
"query(burglary).\n"
"query(earthquake).\n"
"query(calls(mary)).\n";
static void require(int holds, const char *what) {
if (!holds) {
fprintf(stderr, "alarm: %s\n", what);
exit(1);
}
}
int main(void) {
TddlogOptions options = tddlog_options_default();
options.evidence_probability = true;
TddlogSolution *solution = NULL;
if (tddlog_solve(PROGRAM, &options, &solution) != TDDLOG_OK) {
fprintf(stderr, "alarm: %s\n", tddlog_last_error_message());
return 1;
}
require(tddlog_solution_consistent(solution), "the evidence is consistent");
for (size_t i = 0; i < tddlog_solution_num_answers(solution); i++) {
printf("%s:\t%.6f\n", tddlog_solution_query(solution, i),
tddlog_solution_probability(solution, i));
}
double evidence = 0.0, log10_evidence = 0.0;
require(tddlog_solution_evidence_probability(solution, &evidence, &log10_evidence),
"P(e) was asked for");
printf("P(e):\t%.6f\tlog10 %.6f\n", evidence, log10_evidence);
/* The answers against the numbers worked out above. */
const double e = 0.28 * 0.7;
const double expected[] = {0.1 * 0.7 / e, 0.2 * 0.7 / e, 0.4};
const char *queries[] = {"burglary", "earthquake", "calls(mary)"};
require(tddlog_solution_num_answers(solution) == 3, "three answers");
for (size_t i = 0; i < 3; i++) {
require(strcmp(tddlog_solution_query(solution, i), queries[i]) == 0, "the queries in order");
require(fabs(tddlog_solution_probability(solution, i) - expected[i]) < 1e-12,
"the answers worked out by hand");
}
require(fabs(evidence - e) < 1e-12, "P(e) = 0.196");
require(fabs(log10_evidence - log10(e)) < 1e-12, "log10 P(e)");
tddlog_solution_free(solution);
return 0;
}
The guide describes the language tddlog reads, with worked examples that open in the playground.
The header¶
include/tddlog.h, which documents every type and function:
/* tddlog: exact probabilities for ProbLog programs, by compilation into tree
* decision diagrams.
*
* Copyright the tddlog authors. Licensed under the Apache License, Version 2.0.
*
* The ABI is unstable before 1.0: struct layouts, status codes and function
* signatures can change in any release. Build against the tddlog version you
* ship with, and check tddlog_version() at run time.
*/
#ifndef TDDLOG_H
#define TDDLOG_H
/* Generated from bindings/c/src/lib.rs by cbindgen. Do not edit by hand;
* regenerate it with python bindings/c/check.py --write-header. */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* The call succeeded.
*/
#define TDDLOG_OK 0
/**
* The arguments broke a documented contract: a null pointer, text that is
* not UTF-8, an unknown vtree strategy, or options that ask for nothing.
*/
#define TDDLOG_ERROR_INVALID_INPUT 1
/**
* The program is not in the language tddlog reads.
*/
#define TDDLOG_ERROR_PARSE 2
/**
* The program parses but cannot be ground: an unsupported construct or a
* rule that is not range restricted.
*/
#define TDDLOG_ERROR_GROUND 3
/**
* 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.
*/
#define TDDLOG_ERROR_COMPILE 4
/**
* tddlog panicked. The panic did not cross into the caller; report it as a
* bug.
*/
#define TDDLOG_ERROR_PANIC 5
/**
* The answers to a program's queries, and `P(e)` where it was asked for.
* `tddlog_solve` makes one and `tddlog_solution_free` releases it; the
* accessors only read it, so threads may share one.
*/
typedef struct TddlogSolution TddlogSolution;
/**
* What `tddlog_solve` is asked to do: the command line's options. Start from
* `tddlog_options_default` and change what you need.
*/
typedef struct TddlogOptions {
/**
* The vtree strategy, the command line's `--vtree`: `"auto"`, or one of
* the layouts `tddlog --help` lists. Null is `"auto"`.
*/
const char *vtree;
/**
* Also compute `P(e)`, the probability of the evidence, which
* `tddlog_solution_evidence_probability` then gives. The evidence no
* query depends on is then compiled rather than dropped.
*/
bool evidence_probability;
/**
* Answer the program's queries. `false` leaves them out, as the command
* line's `--no-queries` does, and needs `evidence_probability`.
*/
bool queries;
} TddlogOptions;
/**
* What `tddlog_solve` returned: `TDDLOG_OK`, or one of the `TDDLOG_ERROR_`
* values. `tddlog_last_error_message` describes the failure in words.
*/
typedef int32_t TddlogStatus;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* The version of these bindings, which is the version of the tddlog release
* they wrap. The string is static and outlives every other call.
*/
const char *tddlog_version(void);
/**
* Why the last `tddlog_solve` on this thread failed, as a NUL-terminated
* string: the message the command line prints. The empty string where it
* succeeded. Never null.
*
* The message belongs to tddlog and is replaced by the next `tddlog_solve`
* on the same thread; copy it to keep it.
*/
const char *tddlog_last_error_message(void);
/**
* The defaults: `"auto"`, the queries answered, no `P(e)`.
*/
struct TddlogOptions tddlog_options_default(void);
/**
* Parse, ground, compile and count `program`, a ProbLog program as
* NUL-terminated UTF-8 text, under `options` (null for the defaults).
*
* On `TDDLOG_OK`, `*out` is a solution the caller releases with
* `tddlog_solution_free`; on any other status `*out` is null.
*
* # Safety
*
* `program` must point to a NUL-terminated string, `options` must be null or
* point to one `TddlogOptions` whose `vtree` is null or a NUL-terminated
* string, and `out` must point to storage for one pointer.
*/
TddlogStatus tddlog_solve(const char *program,
const struct TddlogOptions *options,
struct TddlogSolution **out);
/**
* Release a solution `tddlog_solve` made. Null is accepted.
*
* # Safety
*
* `solution` must be null or a solution from `tddlog_solve` that nothing has
* freed since.
*/
void tddlog_solution_free(struct TddlogSolution *solution);
/**
* Whether the evidence is consistent. Where it is not, the solution has no
* answers, and `P(e)`, where asked for, is zero.
*
* # Safety
*
* `solution` must be a live solution from `tddlog_solve`.
*/
bool tddlog_solution_consistent(const struct TddlogSolution *solution);
/**
* The number of answers: one per query of the program, in its order; zero
* where the evidence is inconsistent or the queries were left out.
*
* # Safety
*
* `solution` must be a live solution from `tddlog_solve`.
*/
size_t tddlog_solution_num_answers(const struct TddlogSolution *solution);
/**
* The query of answer `index`, as ProbLog prints it (`\+p` for a
* `query(\+p)`), or null where `index` is not below
* `tddlog_solution_num_answers`. The string belongs to the solution and
* lives until it is freed.
*
* # Safety
*
* `solution` must be a live solution from `tddlog_solve`.
*/
const char *tddlog_solution_query(const struct TddlogSolution *solution, size_t index);
/**
* The probability of answer `index`, or NaN where `index` is not below
* `tddlog_solution_num_answers`.
*
* # Safety
*
* `solution` must be a live solution from `tddlog_solve`.
*/
double tddlog_solution_probability(const struct TddlogSolution *solution, size_t index);
/**
* `P(e)`, the probability of the evidence, where `options.evidence_probability`
* asked for it: writes it to `*probability` and its base-10 logarithm, exact
* beyond a double's range, to `*log10_probability`, and returns true. `P(e)`
* is 1 with no evidence, 0 with inconsistent evidence (its logarithm
* `-INFINITY`), and 0 too where it is under the least double. Returns false,
* writing nothing, where it was not asked for. Either output may be null.
*
* # Safety
*
* `solution` must be a live solution from `tddlog_solve`, and each output
* null or a pointer to storage for one double.
*/
bool tddlog_solution_evidence_probability(const struct TddlogSolution *solution,
double *probability,
double *log10_probability);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif /* TDDLOG_H */