Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HarvardPL/AbcDatalog/llms.txt

Use this file to discover all available pages before exploring further.

Once a DatalogExecutor has been started, you can push new EDB facts into the running evaluation at any time using addFactAsynchronously(PositiveAtom). The executor incorporates each fact and derives any consequences that follow, firing registered listeners as new facts are produced. This page walks through a complete working example and explains the rules that govern which facts can be added.

Complete example

The following program computes the transitive closure of a graph, starting with two edges in the initial program. Three more edges — including one that closes a cycle — are added asynchronously after evaluation has started.
public static void main(String[] args) throws DatalogParseException, DatalogValidationException {
    String program =
        "edge(0,1). edge(1,2). tc(X,Y) :- edge(X,Y)."
            + "tc(X,Y) :- edge(X,Z), tc(Z,Y). cycle :- tc(X,X).";
    DatalogTokenizer t = new DatalogTokenizer(new StringReader(program));
    Set<Clause> ast = DatalogParser.parseProgram(t);
    PredicateSym edge = PredicateSym.create("edge", 2);
    PredicateSym tc = PredicateSym.create("tc", 2);
    PredicateSym cycle = PredicateSym.create("cycle", 0);

    // 1. Initialize the executor.

    DatalogParallelExecutor ex = new DatalogParallelExecutor();
    ex.initialize(ast, Collections.singleton(edge));

    // 2. Register listeners.

    // Every time a new tuple is added to the transitive closure relation,
    // print it out.
    ex.registerListener(
        tc,
        fact -> {
          synchronized (System.out) {
            System.out.println("Fact derived: " + fact);
          }
        });

    // Notify us if a cycle is detected.
    ex.registerListener(
        cycle,
        fact -> {
          synchronized (System.out) {
            System.out.println("*** Cycle detected. ***");
          }
        });

    // 3. Start the executor.

    ex.start();

    // 4. Add new facts to the executor.

    String newFacts = "edge(2,3). edge(3,4). edge(4,0).";
    t = new DatalogTokenizer(new StringReader(newFacts));
    while (t.hasNext()) {
      waitAndAdd(ex, DatalogParser.parseClauseAsPositiveAtom(t));
    }

    ex.shutdown();
}

Step-by-step breakdown

1

Parse the Datalog program

Use DatalogTokenizer and DatalogParser.parseProgram() to turn the program string into a Set<Clause>. The program here encodes transitive closure (tc) and a zero-arity cycle detector (cycle). The initial EDB facts (edge(0,1) and edge(1,2)) are embedded directly in the program string.
DatalogTokenizer t = new DatalogTokenizer(new StringReader(program));
Set<Clause> ast = DatalogParser.parseProgram(t);
2

Create a DatalogParallelExecutor

Instantiate DatalogParallelExecutor, the concrete implementation of DatalogExecutor that runs evaluation concurrently in separate threads.
DatalogParallelExecutor ex = new DatalogParallelExecutor();
3

Call initialize()

Pass the parsed program and a set of PredicateSym values representing which EDB predicates may be extended at runtime. In this example, only the edge predicate is extendible. Any fact added later via addFactAsynchronously() must use one of these predicates.
ex.initialize(ast, Collections.singleton(edge));
4

Register listeners

Register DatalogListener callbacks before calling start(). See Reacting to derived facts with DatalogListener for full details on writing listeners safely.
ex.registerListener(tc, fact -> { ... });
ex.registerListener(cycle, fact -> { ... });
5

Call start()

Start the evaluation. The executor begins processing the initial facts and rules in background threads. After this point, listener registration is no longer allowed.
ex.start();
6

Add facts with addFactAsynchronously()

Parse each new fact as a PositiveAtom and pass it to addFactAsynchronously(). The executor enqueues the fact and incorporates it into the running evaluation. Any derivations triggered by the new fact will be processed concurrently and can cause listener callbacks to fire.
String newFacts = "edge(2,3). edge(3,4). edge(4,0).";
t = new DatalogTokenizer(new StringReader(newFacts));
while (t.hasNext()) {
    waitAndAdd(ex, DatalogParser.parseClauseAsPositiveAtom(t));
}
7

Call shutdown()

Shut down the executor once you have finished adding facts. Shutdown signals the evaluation to complete any pending work and release its resources.
ex.shutdown();

Rules for addFactAsynchronously()

addFactAsynchronously(PositiveAtom edbFact) enforces the following constraints:
  • The fact must be ground. A ground atom contains no variables — all argument positions must be bound to concrete constants. If edbFact.isGround() returns false, the method throws IllegalArgumentException.
  • The predicate must be extendible. The predicate symbol of edbFact must appear in the extendibleEdbPreds set passed to initialize(). If it does not, the method throws IllegalArgumentException with the message "Atom is not part of an extendible EDB relation.".
  • The executor must be initialized. Calling this method before initialize() throws IllegalStateException.
addFactAsynchronously() is thread-safe. Multiple threads can safely call it concurrently on the same executor, making it straightforward to feed facts from several producer threads simultaneously.
After shutdown() is called, the executor cannot be reused. To run another evaluation, create a new DatalogParallelExecutor instance.

Build docs developers (and LLMs) love