tddlog for C

Exact probabilistic inference from C and C++.

tddlog computes exact probabilities for ProbLog programs, LPADs, Dice programs, Circulette netlists and Bayesian and Markov networks. This library exposes the same Rust implementation as the command line through a C interface. A program goes in as text, 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.

Install

The release has a prebuilt archive for Linux x86-64 and ARM64, macOS ARM64 and x86-64, and Windows x86-64. It needs no Rust toolchain. On Linux x86-64:

curl -L https://github.com/Tractables/tddlog/releases/download/v0.1.1/tddlog-c-v0.1.1-x86_64-unknown-linux-gnu.tar.gz | tar xz
export PKG_CONFIG_PATH=$PWD/tddlog-c-v0.1.1-x86_64-unknown-linux-gnu/lib/pkgconfig
cc prog.c $(pkg-config --cflags --libs tddlog) -o prog
  • A .tar.gz holds the header in include/, the shared and static libraries, the CMake package and lib/pkgconfig in lib/, and the license and notices in share/licenses/tddlog.

  • With CMake, set CMAKE_PREFIX_PATH to the unpacked directory, call find_package(tddlog CONFIG REQUIRED) and link tddlog::tddlog. The CMake package is for Linux and macOS.

  • The Windows zip holds the header, tddlog_c.dll, its import library tddlog_c.dll.lib and the static tddlog_c.lib.

  • NATIVE-STATIC-LIBS.txt in each archive names the system libraries a static link needs.

  • The dynamic loader has to find the library at run time: an rpath, LD_LIBRARY_PATH on Linux or DYLD_LIBRARY_PATH on macOS.

Getting started

This program answers the queries of program, a ProbLog program as text, and the probability of its evidence:

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 runs it on the alarm example and checks the answers. It calls log10, so it links the maths library too:

cc bindings/c/examples/alarm.c $(pkg-config --cflags --libs tddlog) -lm -o alarm
  • TddlogOptions holds the command line’s options: vtree, evidence_probability and queries. Null options are the defaults.

  • format says what the text is: "problog" or null (LPADs included), "dice", "circulette", or a network’s "bif", "hugin", "xdsl" or "uai".

  • evidence is the text of a network’s evidence file (Ace’s .inst or UAI’s .evid).

  • arguments are strings that take the program’s places $1, $2, …

  • tddlog_solve returns TDDLOG_OK or an error status, and tddlog_last_error_message() gives the command line’s message.

  • The answers are in the order of the program’s queries. Where the evidence is inconsistent, tddlog_solution_consistent is false and there are none.

  • The caller frees the solution with tddlog_solution_free. Its query strings live until then.

  • tddlog is single-threaded. Each thread may solve its own program, and error messages are kept per thread.

The header documents every type, function and status code. The guide describes the language, and its worked examples open in the playground. The website has the benchmark results.

Build from source

This needs Rust 1.88 or newer (rustup installs it), a C++20 compiler, a C compiler, make and CMake 3.16 or newer. The guide’s getting started says what to do if the build fails.

git clone --depth 1 https://github.com/Tractables/tddlog
cd tddlog
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 is laid out as the archive above. Without CMake, as on Windows, cargo builds the libraries in bindings/c/target/release, and the header is bindings/c/include/tddlog.h:

cargo build --release --manifest-path bindings/c/Cargo.toml

A static link also needs the C++ runtime and the system libraries that cargo rustc --release --manifest-path bindings/c/Cargo.toml -- --print native-static-libs prints. On Linux:

cc -Ibindings/c/include prog.c bindings/c/target/release/libtddlog_c.a \
   -lstdc++ -lgcc_s -lutil -lrt -lpthread -lm -ldl -lc -o prog

python bindings/c/check.py builds, tests and installs the package, then builds the example against it through CMake and pkg-config. include/tddlog.h is generated from src/lib.rs by cbindgen and committed. After changing src/lib.rs, run python bindings/c/check.py --write-header.

Acknowledgements

This material is based upon work supported by the Defense Advanced Research Projects Agency (DARPA) under Agreement No. HR0011269E224.