Skip to content

Commit

Permalink
performance - evaluate non-recursive rules only once in stratified ev…
Browse files Browse the repository at this point in the history
…aluation
  • Loading branch information
madmike200590 committed Sep 1, 2020
1 parent 92deac9 commit 5a67786
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 48 deletions.
21 changes: 11 additions & 10 deletions src/main/java/at/ac/tuwien/kr/alpha/common/rule/InternalRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
import at.ac.tuwien.kr.alpha.grounder.Unifier;

/**
* Represents a normal rule or a constraint for the semi-naive grounder. A normal rule has one (or no if it's a constraint) atom in it's head.
* Represents a normal rule or a constraint for the semi-naive grounder. A normal rule has one (or no if it's a
* constraint) atom in it's head.
*/
public class InternalRule extends NormalRule {

Expand All @@ -63,9 +64,6 @@ public InternalRule(NormalHead head, List<Literal> body) {
}
this.ruleId = InternalRule.ID_GENERATOR.getNextId();

final List<Atom> pos = new ArrayList<>(body.size() / 2);
final List<Atom> neg = new ArrayList<>(body.size() / 2);

this.occurringPredicates = new ArrayList<>();
if (!isConstraint()) {
this.occurringPredicates.add(this.getHeadAtom().getPredicate());
Expand All @@ -75,11 +73,11 @@ public InternalRule(NormalHead head, List<Literal> body) {
if (literal instanceof AggregateLiteral) {
throw new IllegalArgumentException("AggregateLiterals aren't supported in InternalRules! (lit: " + literal.toString() + ")");
}
(literal.isNegated() ? neg : pos).add(literal.getAtom());
this.occurringPredicates.add(literal.getPredicate());
}

// not needed, done in AbstractRule! Leaving it commented out for future reference since this might actually be the proper place to put it
// not needed, done in AbstractRule! Leaving it commented out for future reference since this might actually be the
// proper place to put it
// this.checkSafety();

this.groundingOrders = new RuleGroundingOrders(this);
Expand All @@ -89,14 +87,15 @@ public InternalRule(NormalHead head, List<Literal> body) {
@VisibleForTesting
public static void resetIdGenerator() {
InternalRule.ID_GENERATOR.resetGenerator();
}
}

public static InternalRule fromNormalRule(NormalRule rule) {
return new InternalRule(rule.isConstraint() ? null : new NormalHead(rule.getHeadAtom()), new ArrayList<>(rule.getBody()));
}

/**
* Returns a new Rule that is equal to this one except that all variables are renamed to have the newVariablePostfix appended.
* Returns a new Rule that is equal to this one except that all variables are renamed to have the newVariablePostfix
* appended.
*
* @param newVariablePostfix
* @return
Expand All @@ -123,7 +122,8 @@ public InternalRule renameVariables(String newVariablePostfix) {

/**
*
* @return a list of all ordinary predicates occurring in the rule (may contain duplicates, does not contain builtin atoms).
* @return a list of all ordinary predicates occurring in the rule (may contain duplicates, does not contain builtin
* atoms).
*/
public List<Predicate> getOccurringPredicates() {
return this.occurringPredicates;
Expand All @@ -136,4 +136,5 @@ public RuleGroundingOrders getGroundingOrders() {
public int getRuleId() {
return this.ruleId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public AnswerSet assignmentToAnswerSet(Iterable<Integer> trueAtoms) {
// TODO remove, debugging help
private void dbgPrintCounts() {
System.out.println("********** GROUNDING INFO ***********");
for(InternalRule rule : this.ruleEvaluationCounts.keySet()) {
for (InternalRule rule : this.ruleEvaluationCounts.keySet()) {
System.out.println("Rule: " + rule + ", evaluations = " + this.ruleEvaluationCounts.get(rule) + ", non-unique-substitutions = " + this.ruleSubstitutionCounts.get(rule));
}
System.out.println("******* END OF GROUNDING INFO *******");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.ac.tuwien.kr.alpha.grounder.transformation;

import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -101,7 +102,7 @@ public InternalProgram apply(AnalyzedProgram inputProgram) {

// TODO remove, debugging help
this.dbgPrintCounts();

// Build the program resulting from evaluating the stratified part.
List<Atom> outputFacts = this.buildOutputFacts(inputProgram.getFacts(), this.additionalFacts);
List<InternalRule> outputRules = new ArrayList<>();
Expand All @@ -114,12 +115,13 @@ public InternalProgram apply(AnalyzedProgram inputProgram) {
// TODO remove, debugging help
private void dbgPrintCounts() {
System.out.println("********** GROUNDING INFO ***********");
for(InternalRule rule : this.ruleEvaluationCounts.keySet()) {
System.out.println("Rule: " + rule + ", evaluations = " + this.ruleEvaluationCounts.get(rule) + ", non-unique-substitutions = " + this.ruleSubstitutionCounts.get(rule));
for (InternalRule rule : this.ruleEvaluationCounts.keySet()) {
System.out.println("Rule: " + rule + ", evaluations = " + this.ruleEvaluationCounts.get(rule) + ", non-unique-substitutions = "
+ this.ruleSubstitutionCounts.get(rule));
}
System.out.println("******* END OF GROUNDING INFO *******");
}

// extra method is better visible in CPU traces when profiling
private List<Atom> buildOutputFacts(List<Atom> initialFacts, Set<Atom> newFacts) {
Set<Atom> atomSet = new LinkedHashSet<>(initialFacts);
Expand All @@ -129,36 +131,47 @@ private List<Atom> buildOutputFacts(List<Atom> initialFacts, Set<Atom> newFacts)

private void evaluateComponent(SCComponent comp) {
LOGGER.debug("Evaluating component {}", comp);
Set<InternalRule> rulesToEvaluate = this.getRulesToEvaluate(comp);
if (rulesToEvaluate.isEmpty()) {
ComponentEvaluationInfo evaluationInfo = this.getRulesToEvaluate(comp);
if (evaluationInfo.isEmpty()) {
LOGGER.debug("No rules to evaluate for component {}", comp);
return;
}
Map<Predicate, List<Instance>> addedInstances = new HashMap<>();
this.prepareComponentEvaluation(rulesToEvaluate);
do {
this.workingMemory.reset();
LOGGER.debug("Starting component evaluation run...");
for (InternalRule r : rulesToEvaluate) {
this.evaluateRule(r);
}
this.modifiedInLastEvaluationRun = new HashMap<>();
// Since we're stratified we never have to backtrack, therefore just collect the added instances.
for (IndexedInstanceStorage instanceStorage : this.workingMemory.modified()) {
// NOTE: We're only dealing with positive instances.
addedInstances.putIfAbsent(instanceStorage.getPredicate(), new ArrayList<>());
addedInstances.get(instanceStorage.getPredicate()).addAll(instanceStorage.getRecentlyAddedInstances());
this.modifiedInLastEvaluationRun.putIfAbsent(instanceStorage.getPredicate(), new LinkedHashSet<>());
this.modifiedInLastEvaluationRun.get(instanceStorage.getPredicate()).addAll(instanceStorage.getRecentlyAddedInstances());
instanceStorage.markRecentlyAddedInstancesDone();

}
// If evaluation of rules doesn't modify the working memory we have a fixed point.
} while (!this.workingMemory.modified().isEmpty());
this.prepareComponentEvaluation(SetUtils.union(evaluationInfo.nonRecursiveRules, evaluationInfo.recursiveRules));
// Rules outside of dependency cycles only need to be evaluated once.
if (!evaluationInfo.nonRecursiveRules.isEmpty()) {
this.addFactsToProgram(this.evaluateRules(evaluationInfo.nonRecursiveRules));
}
if (!evaluationInfo.recursiveRules.isEmpty()) {
do {
// Now do the rules that cyclically depend on each other,
// evaluate these until nothing new can be derived any more.
this.addFactsToProgram(this.evaluateRules(evaluationInfo.recursiveRules));
// If evaluation of rules doesn't modify the working memory we have a fixed point.
} while (!this.workingMemory.modified().isEmpty());
}
LOGGER.debug("Evaluation done - reached a fixed point on component {}", comp);
this.addFactsToProgram(addedInstances);
rulesToEvaluate.forEach((rule) -> this.solvedRuleIds.add(rule.getRuleId()));
LOGGER.debug("Finished adding program facts");
SetUtils.union(evaluationInfo.nonRecursiveRules, evaluationInfo.recursiveRules)
.forEach((rule) -> this.solvedRuleIds.add(rule.getRuleId()));
}

private Map<Predicate, List<Instance>> evaluateRules(Set<InternalRule> rules) {
Map<Predicate, List<Instance>> addedInstances = new HashMap<>();
this.workingMemory.reset();
LOGGER.debug("Starting component evaluation run...");
for (InternalRule r : rules) {
this.evaluateRule(r);
}
this.modifiedInLastEvaluationRun = new HashMap<>();
// Since we're stratified we never have to backtrack, therefore just collect the added instances.
for (IndexedInstanceStorage instanceStorage : this.workingMemory.modified()) {
// NOTE: We're only dealing with positive instances.
addedInstances.putIfAbsent(instanceStorage.getPredicate(), new ArrayList<>());
addedInstances.get(instanceStorage.getPredicate()).addAll(instanceStorage.getRecentlyAddedInstances());
this.modifiedInLastEvaluationRun.putIfAbsent(instanceStorage.getPredicate(), new LinkedHashSet<>());
this.modifiedInLastEvaluationRun.get(instanceStorage.getPredicate()).addAll(instanceStorage.getRecentlyAddedInstances());
instanceStorage.markRecentlyAddedInstancesDone();
}
return addedInstances;
}

/**
Expand Down Expand Up @@ -275,16 +288,33 @@ private void fireRule(InternalRule rule, Substitution substitution) {
this.workingMemory.addInstance(newAtom, true);
}

private Set<InternalRule> getRulesToEvaluate(SCComponent comp) {
Set<InternalRule> retVal = new HashSet<>();
HashSet<InternalRule> tmpPredicateRules;
private ComponentEvaluationInfo getRulesToEvaluate(SCComponent comp) {
Set<InternalRule> nonRecursiveRules = new HashSet<>();
Set<InternalRule> recursiveRules = new HashSet<>();
HashSet<InternalRule> definingRules;
Set<Predicate> headPredicates = new HashSet<>();
for (Node node : comp.getNodes()) {
tmpPredicateRules = this.predicateDefiningRules.get(node.getPredicate());
if (tmpPredicateRules != null) {
retVal.addAll(tmpPredicateRules);
headPredicates.add(node.getPredicate());
}
for (Predicate headPredicate : headPredicates) {
definingRules = this.predicateDefiningRules.get(headPredicate);
if (definingRules == null) {
// predicate only occurs in facts
continue;
}
for (InternalRule rule : definingRules) {
for (Literal lit : rule.getPositiveBody()) {
if (headPredicates.contains(lit.getPredicate())) {
// rule body contains a predicate that is defined in the same component,
// rule is therefore part of a dependency cycle and must be evaluated repeatedly
recursiveRules.add(rule);
} else {
nonRecursiveRules.add(rule);
}
}
}
}
return retVal;
return new ComponentEvaluationInfo(nonRecursiveRules, recursiveRules);
}

private void addFactsToProgram(Map<Predicate, List<Instance>> instances) {
Expand Down Expand Up @@ -346,4 +376,27 @@ public SCComponent next() {
}
}

/**
* Internal helper class to group rules within an {@SCComponent} into rules that are recursive, i.e. part of some cyclic
* dependency chain within that component, and non-recursive rules, i.e. rules where all body predicates occur in lower
* strata. The reason for this grouping is that, when evaluating rules within a component, non-recursive rules only need
* to be evaluated once, while recursive rules need to be evaluated until a fixed-point has been reached.
*
* Copyright (c) 2020, the Alpha Team.
*/
private class ComponentEvaluationInfo {
final Set<InternalRule> nonRecursiveRules;
final Set<InternalRule> recursiveRules;

ComponentEvaluationInfo(Set<InternalRule> nonRecursive, Set<InternalRule> recursive) {
this.nonRecursiveRules = Collections.unmodifiableSet(nonRecursive);
this.recursiveRules = Collections.unmodifiableSet(recursive);
}

boolean isEmpty() {
return this.nonRecursiveRules.isEmpty() && this.recursiveRules.isEmpty();
}

}

}

0 comments on commit 5a67786

Please sign in to comment.