All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.computablefacts.decima.problog.Subgoal Maven / Gradle / Ivy

Go to download

Decima is a proof-of-concept Java implementation of the probabilistic logic programming language ProbLog.

There is a newer version: 1.39
Show newest version
package com.computablefacts.decima.problog;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.computablefacts.decima.robdd.Pair;
import com.computablefacts.decima.trie.Trie;
import com.computablefacts.nona.Generated;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Var;

/**
 * A subgoal is the item that is tabled by this algorithm.
 *
 * A subgoal has a literal, a set of facts, and an array of waiters. A waiter is a pair containing a
 * subgoal and a clause.
 */
@CheckReturnValue
final public class Subgoal {

  // KB rules benefiting from this sub-goal resolution
  public final Trie trie_ = new Trie<>();
  public final Set parents_ = new HashSet<>();

  // All maps and lists should support concurrency because they will be updated and enumerated at
  // the same time by the tabling algorithm
  private final int id_;
  private final Literal literal_;

  // Parent rules benefiting from this sub-goal resolution
  private final Set> waiters_ =
      Collections.newSetFromMap(new ConcurrentHashMap<>());

  // Facts derived for this subgoal
  private final Set facts_ = Collections.newSetFromMap(new ConcurrentHashMap<>());

  Subgoal(int id, Literal literal) {

    Preconditions.checkArgument(id >= 0, "id must be >= 0");
    Preconditions.checkNotNull(literal, "literal should not be null");

    id_ = id;
    literal_ = literal;
  }

  Subgoal(Subgoal subgoal) {

    this(subgoal.id_, subgoal.literal_);

    // TODO : copy trie
    facts_.addAll(subgoal.facts_);

    subgoal.waiters_.forEach(
        e -> waiters_.add(new AbstractMap.SimpleEntry<>(new Subgoal(e.getKey()), e.getValue())));
  }

  @Override
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (!(o instanceof Subgoal)) {
      return false;
    }
    Subgoal subgoal = (Subgoal) o;
    return Objects.equals(literal_, subgoal.literal_);
  }

  @Override
  public int hashCode() {
    return Objects.hash(literal_);
  }

  @Generated
  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this).add("id", id_).add("literal", literal_)
        .add("facts", facts_).add("parents", parents_).omitNullValues().toString();
  }

  int id() {
    return id_;
  }

  Literal literal() {
    return literal_;
  }

  Set facts() {
    return facts_;
  }

  Set> waiters() {
    return waiters_;
  }

  void addWaiter(Subgoal subgoal, Clause clause) {

    Preconditions.checkNotNull(subgoal, "subgoal should not be null");
    Preconditions.checkNotNull(clause, "clause should not be null");
    Preconditions.checkArgument(clause.isRule(), "clause should be a rule : %s", clause.toString());

    waiters_.add(new AbstractMap.SimpleEntry<>(subgoal, clause));
  }

  Set groundedRules() {
    return new HashSet<>(parents_);
  }

  boolean hasFacts() {
    return !facts_.isEmpty();
  }

  boolean containsFact(Clause clause) {

    Preconditions.checkNotNull(clause, "clause should not be null");
    Preconditions.checkArgument(clause.isFact(), "clause should be a fact : %s", clause.toString());

    return facts_.contains(clause);
  }

  void addFact(Clause clause) {

    Preconditions.checkNotNull(clause, "clause should not be null");
    Preconditions.checkArgument(clause.isFact(), "clause should be a fact : %s", clause.toString());

    if (!containsFact(clause)) {
      facts_.add(clause);
    }
  }

  boolean hasRules() {
    return !parents_.isEmpty();
  }

  /**
   * Remove all rules where one of the body literal matches a given literal.
   *
   * @param literal literal.
   */
  void cleanup(Literal literal) {

    Preconditions.checkNotNull(literal, "literal should not be null");

    trie_.remove(lit -> {
      if (lit.isRelevant(literal)) {

        @Var
        int nbMatch = 0;
        @Var
        int nbNoMatch = 0;

        for (int i = 0; i < literal.terms().size(); i++) {

          AbstractTerm term1 = literal.terms().get(i);
          AbstractTerm term2 = lit.terms().get(i);

          // Match const against const and var against var
          if (term1.getClass() == term2.getClass()) {
            nbMatch++;
          } else {
            nbNoMatch++;
          }
        }

        if (nbMatch != 0 && nbNoMatch == 0) {
          return true;
        }
      }
      return false;
    });
  }

  /**
   * Get the rule associated with a given clause.
   *
   * @param clause clause.
   * @return matching rule or null.
   */
  void update(Clause clause) {

    Preconditions.checkNotNull(clause, "clause should not be null");
    Preconditions.checkArgument(clause.isRule(), "clause should be a rule : %s", clause.toString());

    // Due to how the tabling algorithm works, a good candidate rule to update is such that :
    // - the clause body MUST BE a suffix of the candidate rule body
    // - the body literals in [0, candidate.body.size - clause.body.size[ of the candidate rule MUST
    // BE grounded

    List> parents = new ArrayList<>();
    Function>, Boolean> fnSkip = input -> {

      if (input.size() == 1) { // At depth 0 of the trie is the clause head
        return !input.get(0).u.isRelevant(clause.head());
      }
      return false;
    };
    Function>, Void> fnProcess = input -> {

      int max = input.stream().mapToInt(p -> p.t).max().getAsInt();

      if (!parents.isEmpty() && parents.get(0).t >= max) {
        return null;
      }

      Literal head = input.get(0).u;
      List body = input.stream().skip(1).map(p -> p.u).collect(Collectors.toList());
      Clause candidate = new Clause(head, body);

      if (body.size() < clause.body().size()) {
        return null;
      }

      @Var
      boolean isOk = true;

      for (int k = 0; k < body.size(); k++) {

        Literal lit1 = body.get(k);

        if (k < (body.size() - clause.body().size())) {

          // Check if the candidate rule prefix is grounded
          if (!lit1.isGrounded()) {
            isOk = false;
            break;
          }
        } else {

          // Check if the candidate rule suffix is relevant
          Literal lit2 = clause.body().get(k - (body.size() - clause.body().size()));

          if (!lit1.isRelevant(lit2)) {
            isOk = false;
            break;
          }
        }
      }

      if (isOk) {
        parents.clear();
        parents.add(new Pair<>(max, candidate));
      }
      return null;
    };

    trie_.traverse(fnSkip, fnProcess);

    if (parents.isEmpty()) {

      // Quand on a une règle du type (R -> PREFIX LITERAL SUFFIX), et que :
      //
      // - le solveur a résolu chacun des litéraux de PREFIX
      // - le solveur vient de résoudre LITERAL et cherche à mettre à jour la règle parent
      // - le solveur a supprimé la règle parent car la résolution d'une autre règle a flaggé SUFFIX
      // comme contenant un litéral invalide
      //
      // Alors la fusion de LITERAL avec la règle parent (inexistante) échoue.
      return;
    }

    // Only apply substitution to the most recent parent
    Clause parent = parents.get(0).u;
    Clause newParent = merge(clause, parent);

    List list = new ArrayList<>(newParent.body().size() + 1);
    list.add(newParent.head());
    list.addAll(newParent.body());

    trie_.insert(list);

    if (newParent.isGrounded() && clause.body().size() == 1) {
      parents_.add(newParent); // Here, we evaluated the last body literal of a rule
    }
  }

  private Clause merge(Clause clause, Clause candidate) {

    Preconditions.checkNotNull(clause, "clause should not be null");
    Preconditions.checkArgument(clause.isRule(), "clause should be a rule : %s", clause.toString());
    Preconditions.checkNotNull(candidate, "candidate should not be null");
    Preconditions.checkArgument(candidate.isRule(), "candidate should be a rule : %s",
        clause.toString());

    // 1 - Build env
    Map env =
        candidate.head().unify(clause.head());

    for (int i = 0; i < clause.body().size(); i++) {

      Literal lit1 = candidate.body().get(candidate.body().size() - clause.body().size() + i);
      Literal lit2 = clause.body().get(i);

      env.putAll(lit1.unify(lit2));
    }

    // 2 - Fill candidate rule
    Clause merged = candidate.subst(env);

    // 3 - Transfer probabilities to the right literals
    Literal head = merged.head();
    List body =
        new ArrayList<>(merged.body().subList(0, merged.body().size() - clause.body().size()));

    if (!clause.body().isEmpty()) {
      body.add(clause.body().get(0));
      body.addAll(merged.body().subList(merged.body().size() - clause.body().size() + 1,
          merged.body().size()));
    }

    // 4 - Create a new clause
    return new Clause(head, body);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy