diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java index efc26ac66..8c356ac92 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java @@ -1,12 +1,13 @@ package eu.knowledge.engine.reasoner; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -44,22 +45,43 @@ public class ReasonerPlan { private EnumSet matchConfig = EnumSet.noneOf(MatchFlag.class); private boolean useTaskBoard = true; + /** + * @see ReasonerPlan#ReasonerPlan(RuleStore, ProactiveRule, EnumSet) + */ public ReasonerPlan(RuleStore aStore, ProactiveRule aStartRule) { this.store = aStore; this.start = aStartRule; this.ruleToRuleNode = new HashMap<>(); - createOrGetReasonerNode(this.start, null); + createOrGetRuleNode(this.start); } + /** + * Create a new reasoner plan with the given {@link RuleStore}, + * {@link ProactiveRule} and {@link MatchFlag}s. + * + * @param aStore The store into which all the rules are available to use for + * the plan. + * @param aStartRule The Proactive rule (should be available in {@code aStore}) + * to start the plan with. + * @param aConfig The configuration to use for creating the plan. + */ public ReasonerPlan(RuleStore aStore, ProactiveRule aStartRule, EnumSet aConfig) { this.store = aStore; this.start = aStartRule; this.ruleToRuleNode = new HashMap<>(); this.matchConfig = aConfig; - createOrGetReasonerNode(this.start, null); + createOrGetRuleNode(this.start); } - private RuleNode createOrGetReasonerNode(BaseRule aRule, BaseRule aParent) { + /** + * Creates (or gets) a {@link RuleNode} for the given {@link BaseRule}. It + * recursively creates {@link RuleNode}'s for any neighbors encountered. + * + * @param aRule The {@link BaseRule} for which to create (or get) the + * {@link RuleNode}. + * @return The {@link RuleNode} belonging with the given {@link BaseRule} + */ + private RuleNode createOrGetRuleNode(BaseRule aRule) { final RuleNode currentRuleNode; if (this.ruleToRuleNode.containsKey(aRule)) @@ -71,19 +93,21 @@ private RuleNode createOrGetReasonerNode(BaseRule aRule, BaseRule aParent) { // build the reasoner node graph this.ruleToRuleNode.put(aRule, currentRuleNode); + LOG.trace("Creating RuleNode for: {}", aRule); + if (isBackward()) { // we are only interested in antecedent neighbors. this.store.getAntecedentNeighbors(aRule, this.matchConfig).forEach((rule, matches) -> { if (!(rule instanceof ProactiveRule)) { assert currentRuleNode instanceof AntSide; - var newNode = createOrGetReasonerNode(rule, aRule); + var newNode = createOrGetRuleNode(rule); assert newNode instanceof ConsSide; ((AntSide) currentRuleNode).addAntecedentNeighbour(newNode, matches); var inverseMatches = Match.invertAll(matches); ((ConsSide) newNode).addConsequentNeighbour(currentRuleNode, inverseMatches); } else { - LOG.trace("Skipped proactive rule: {}", rule); + LOG.trace("Skipped proactive rule1: {}", rule); } }); @@ -94,60 +118,171 @@ private RuleNode createOrGetReasonerNode(BaseRule aRule, BaseRule aParent) { } else { // interested in both consequent and antecedent neighbors - this.store.getConsequentNeighbors(aRule, this.matchConfig).forEach((rule, matches) -> { + EnumSet modMatchConfig = EnumSet.copyOf(this.matchConfig); + modMatchConfig.add(MatchFlag.SINGLE_RULE); + + Map> consequentNeighbors = this.store.getConsequentNeighbors(aRule, this.matchConfig); + + consequentNeighbors.forEach((rule, matches) -> { if (!(rule instanceof ProactiveRule)) { assert currentRuleNode instanceof ConsSide; - var newNode = createOrGetReasonerNode(rule, aRule); + var newNode = createOrGetRuleNode(rule); ((ConsSide) currentRuleNode).addConsequentNeighbour(newNode, matches); + Set antCombiMatches = filterAndInvertCombiMatches(rule, aRule, + this.store.getConsequentCombiMatches(currentRuleNode.getRule())); + + var existing = ((AntSide) newNode).getAntecedentCombiMatches(); + + if (existing == null) + ((AntSide) newNode).setAntecedentCombiMatches(antCombiMatches); + else + existing.addAll(antCombiMatches); + var inverseMatches = Match.invertAll(matches); ((AntSide) newNode).addAntecedentNeighbour(currentRuleNode, inverseMatches); } else { - LOG.trace("Skipped proactive rule: {}", rule); + LOG.trace("Skipped proactive rule2: {}", rule); } }); - // store the combi matches when there are any antecedents. - if (!currentRuleNode.getRule().getConsequent().isEmpty()) - ((ConsSide) currentRuleNode) - .setConsequentCombiMatches(this.store.getConsequentCombiMatches(currentRuleNode.getRule())); + // store the combi matches when there are any consequents. + if (!currentRuleNode.getRule().getConsequent().isEmpty()) { + Set existing = ((ConsSide) currentRuleNode).getConsequentCombiMatches(); + Set consequentCombiMatches = this.store + .getConsequentCombiMatches(currentRuleNode.getRule()); + if (existing == null) { + ((ConsSide) currentRuleNode).setConsequentCombiMatches(consequentCombiMatches); + } else + existing.addAll(consequentCombiMatches); + } // antecedent neighbors to propagate bindings further via backward chaining // determine whether our parent matches us partially - boolean ourAntecedentFullyMatchesParentConsequent = false; + boolean ourAntecedentFullyMatchesAllParentConsequents = false; + boolean allMatch = true; + int count = 0; Map> antecedentNeighbors = this.store.getAntecedentNeighbors(aRule, this.matchConfig); - if (aParent != null && antecedentNeighbors.containsKey(aParent)) { - ourAntecedentFullyMatchesParentConsequent = antecedentFullyMatchesConsequent(aRule, aParent, - antecedentNeighbors.get(aParent)); + for (BaseRule neighborRule : antecedentNeighbors.keySet()) { + if (this.isAncestorOfStartNode(neighborRule)) { + allMatch &= antecedentFullyMatchesConsequent(aRule, neighborRule, + antecedentNeighbors.get(neighborRule)); + count++; + } } - if (!ourAntecedentFullyMatchesParentConsequent) { + if (count > 0 && allMatch) + ourAntecedentFullyMatchesAllParentConsequents = true; + if (!ourAntecedentFullyMatchesAllParentConsequents) { antecedentNeighbors.forEach((rule, matches) -> { - if (!(rule instanceof ProactiveRule)) { - assert currentRuleNode instanceof AntSide; - var newNode = createOrGetReasonerNode(rule, aRule); - assert newNode instanceof ConsSide; - ((AntSide) currentRuleNode).addAntecedentNeighbour(newNode, matches); - - var inverseMatches = Match.invertAll(matches); - ((ConsSide) newNode).addConsequentNeighbour(currentRuleNode, inverseMatches); - } else { - LOG.trace("Skipped proactive rule: {}", rule); - } + assert currentRuleNode instanceof AntSide; + var newNode = createOrGetRuleNode(rule); + assert newNode instanceof ConsSide; + ((AntSide) currentRuleNode).addAntecedentNeighbour(newNode, matches); + + var inverseMatches = Match.invertAll(matches); + ((ConsSide) newNode).addConsequentNeighbour(currentRuleNode, inverseMatches); }); - if (!currentRuleNode.getRule().getAntecedent().isEmpty()) - ((AntSide) currentRuleNode) - .setAntecedentCombiMatches(this.store.getAntecedentCombiMatches(currentRuleNode.getRule())); + if (!currentRuleNode.getRule().getAntecedent().isEmpty()) { + var existing = ((AntSide) currentRuleNode).getAntecedentCombiMatches(); + Set antecedentCombiMatches = this.store + .getAntecedentCombiMatches(currentRuleNode.getRule()); + if (existing == null) { + ((AntSide) currentRuleNode).setAntecedentCombiMatches(antecedentCombiMatches); + } else + existing.addAll(antecedentCombiMatches); + } } } return currentRuleNode; } + /** + * Checks if the given rule is an ancestor of the Proactive start node of this + * reasoner plan. With ancestor we mean whether starting from the proactive node + * we can reach the given node following arrows in the same direction. + * + * @param aRule The rule to check. + * @return Whether the given rule is an ancestor. + */ + private boolean isAncestorOfStartNode(BaseRule aRule) { + return isAncestorOfStartNode(aRule, new ArrayList()); + } + + /** + * Check whether the given BaseRule is an ancestor of the start node of this + * reasoning plan. + * + * @param aRule The rule to check whether it is an ancestor. + * @return {@code true} when {@code aRule} is an ancestor, {@code false} + * otherwise. + */ + private boolean isAncestorOfStartNode(BaseRule aRule, List visited) { + + if (visited.contains(aRule)) + return false; + + visited.add(aRule); + + boolean isAncestor = false; + + if (this.getStartNode().getRule().equals(aRule)) { + isAncestor = true; + } else { + Map> antecedentNeighbors = this.store.getAntecedentNeighbors(aRule); + + for (BaseRule antecedentNeighbor : antecedentNeighbors.keySet()) { + isAncestor |= isAncestorOfStartNode(antecedentNeighbor, visited); + } + } + return isAncestor; + } + + /** + * Filters and inverts the given set of combi matches that connect the given + * {@code antRule} and {@code consRule}. + * + * @param antRule The antecedent rule for which the given combi + * matches were created. + * @param consRule The consequent rule for which the given combi + * matches are being inversed. + * @param consequentCombiMatches The combi matches to filter and invert. + * @return Only those combimatches that contain antRule and contain the same + * triple patterns as the antecedent of antRule. + */ + private Set filterAndInvertCombiMatches(BaseRule antRule, BaseRule consRule, + Set consequentCombiMatches) { + + Set filteredAndInvertedCombiMatches = new HashSet(); + + CombiMatch newCm; + for (CombiMatch cm : consequentCombiMatches) { + if (cm.containsKey(antRule)) { + newCm = new CombiMatch(); + Set matches = cm.get(antRule); + Set newMatches = new HashSet(); + for (Match m : matches) { + + if (m.getMatchingPatterns().size() > 0 + && m.getMatchingPatterns().size() == antRule.getAntecedent().size()) { + newMatches.add(m.inverse()); + } + } + if (!newMatches.isEmpty()) { + newCm.put(consRule, newMatches); + filteredAndInvertedCombiMatches.add(newCm); + } + } + } + + return filteredAndInvertedCombiMatches; + } + /** * Enable (default) or disable the {@link TaskBoard}. When it is disabled, all * tasks will be executed as they occur in the algorithm, and an EMPTY task @@ -239,6 +374,8 @@ public TaskBoard execute(BindingSet bindingSet) { var translated = toBeResultPropagated.translate(n.getRule().getAntecedent(), Match.invertAll(matches)); + LOG.trace("EEK: {}", n); + TripleVarBindingSet beforeBindingSet = n.getResultBindingSetInput(); boolean itChanged = ((AntSide) n).addResultBindingSetInput(current, translated); TripleVarBindingSet afterBindingSet = n.getResultBindingSetInput(); diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/BindingSetStore.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/BindingSetStore.java index 812f27f93..ceaa76b1b 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/BindingSetStore.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/BindingSetStore.java @@ -10,6 +10,8 @@ import java.util.stream.Collectors; import org.apache.jena.graph.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import eu.knowledge.engine.reasoner.BaseRule; import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; @@ -32,6 +34,8 @@ public class BindingSetStore { private final Set neighbors; private final Map> neighborBindingSet = new HashMap<>(); + private static final Logger LOG = LoggerFactory.getLogger(BindingSetStore.class); + /** * This combi matches set need to be initialized after the * constructor has been called. @@ -104,7 +108,12 @@ private TripleVarBindingSet translateWithCombiMatches(Set aGraphP Set someCombiMatches, Map> someNeighborBS) { var combinedTVBS = new TripleVarBindingSet(aGraphPattern); + int i = 0; + int size = someCombiMatches.size(); for (CombiMatch cMatch : someCombiMatches) { + i = i + 1; + LOG.trace("Creating binding set for combi match: {}/{}", i, size); + // keep separate binding set per combi match var cMatchTVBS = new TripleVarBindingSet(aGraphPattern); @@ -160,6 +169,7 @@ public TripleVarBindingSet get() { if (this.combiMatches != null) this.cache = this.translateWithCombiMatches(this.graphPattern, this.combiMatches, this.neighborBindingSet); else { + LOG.trace("Ignoring combi matches for binding set construction."); TripleVarBindingSet combinedBS = new TripleVarBindingSet(graphPattern); for (TripleVarBindingSet bs : this.neighborBindingSet.values().stream().map(x -> x.values()) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/FullRuleNode.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/FullRuleNode.java index a19ac6501..a98340a42 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/FullRuleNode.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/FullRuleNode.java @@ -177,15 +177,8 @@ public void transformFilterBS() { @Override public boolean readyForApplyRule() { boolean isReady; - // TODO: This (the "Except" part) was needed to make transitivity work, but not - // sure if it is correct - - if (!this.resultBindingSetInput.get().isEmpty()) { - Set exceptNodes = this.getAllSameLoopNeighbors(); - isReady = this.resultBindingSetInput.haveAllNeighborsContributedExcept(exceptNodes); - } else { - isReady = false; - } + Set exceptNodes = this.getAllSameLoopNeighbors(); + isReady = this.resultBindingSetInput.haveAllNeighborsContributedExcept(exceptNodes); return isReady; } diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/MatchNode.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/MatchNode.java index e1002f634..ecaa40850 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/MatchNode.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/MatchNode.java @@ -100,7 +100,7 @@ public void setAntecedentNeighbor(BaseRule aRule, Set someMatches) { * @param someCombiMatches The combi matches for this node on the consequent * side. */ - public void setConsequentMatches(Set someCombiMatches) { + public void setConsequentCombiMatches(Set someCombiMatches) { this.consequentCombiMatches = someCombiMatches; } diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/RuleStore.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/RuleStore.java index ca02f18f4..483465ac7 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/RuleStore.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulestore/RuleStore.java @@ -167,9 +167,11 @@ public Map> getConsequentNeighbors(BaseRule aRule, EnumSet< Set combiMatches = BaseRule.getMatches(aRule, this.getRules(), false, aConfig); // store combi matches - Map> newMapping = convertToMapping(combiMatches); + aMatchNode.setConsequentCombiMatches(combiMatches); // store normal matches + Map> newMapping = convertToMapping(combiMatches); + for (Map.Entry> entry : newMapping.entrySet()) { aMatchNode.setConsequentNeighbor(entry.getKey(), Match.invertAll(entry.getValue())); this.ruleToMatchNode.get(entry.getKey()).setAntecedentNeighbor(aRule, entry.getValue()); @@ -203,19 +205,19 @@ public void printGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { String color = "red"; - String width = "2"; + String width = "1.5"; StringBuilder sb = new StringBuilder(); sb.append("strict digraph {\n"); - Map ruleToName = new HashMap<>(); + Map ruleToId = new HashMap<>(); for (MatchNode r : ruleToMatchNode.values()) { - String currentName = ruleToName.get(r.getRule()); - if (currentName == null) { - currentName = /* "rule" + ruleNumber; */ generateName(r.getRule()); - assert !currentName.isEmpty(); + String currentId = ruleToId.get(r.getRule()); + if (currentId == null) { + currentId = Integer.toHexString(System.identityHashCode(r)); + assert !currentId.isEmpty(); String replaceAll = toStringRule(r.getRule()).replaceAll("\\\"", "\\\\\""); // check the colouring @@ -227,28 +229,30 @@ public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { } } - String shape = "shape=\"circle\""; + String shape = "shape=\"rect\""; + String doubleShape = ""; if (r.getRule() instanceof ProactiveRule) { - shape = "shape=\"doublecircle\""; + doubleShape = ", peripheries=2"; } - sb.append(currentName).append("[").append(shape).append(pen).append("tooltip=").append("\"") - .append(replaceAll).append("\"").append("]").append("\n"); - ruleToName.put(r.getRule(), currentName); + sb.append(q(currentId)).append("[").append(shape).append(", ").append(pen).append("tooltip=") + .append("\"").append(replaceAll).append("\"").append(doubleShape).append(", label=") + .append(generateName(r.getRule())).append("]").append("\n"); + ruleToId.put(r.getRule(), Integer.toHexString(System.identityHashCode(r))); } Map> antecedentNeighbors = r.getAntecedentNeighbors(); Set anteNeigh = antecedentNeighbors.keySet(); - String neighName; + String neighId; for (BaseRule neighR : anteNeigh) { - neighName = ruleToName.get(neighR); + neighId = ruleToId.get(neighR); - if (neighName == null) { - neighName = /* "rule" + ruleNumber; */ generateName(neighR); - assert !neighName.isEmpty(); - String replaceAll = toStringRule(neighR).replaceAll("\\\"", "\\\\\""); + if (neighId == null) { + neighId = Integer.toHexString(System.identityHashCode(neighR)); + assert !neighId.isEmpty(); + String ruleString = toStringRule(neighR).replaceAll("\\\"", "\\\\\""); // check the colouring String pen = ""; @@ -259,15 +263,17 @@ public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { } } - String shape = "shape=\"circle\""; + String shape = "shape=\"rect\""; + String doubleShape = ""; if (neighR instanceof ProactiveRule) { - shape = "shape=\"doublecircle\""; + doubleShape = ", peripheries=2"; } - sb.append(neighName).append("[").append(shape).append(pen).append("tooltip=").append("\"") - .append(replaceAll).append("\"").append("]").append("\n"); + sb.append(q(neighId)).append("[").append(shape).append(pen).append("tooltip=").append("\"") + .append(ruleString).append("\"").append(doubleShape).append(", label=") + .append(generateName(neighR)).append("").append("]").append("\n"); - ruleToName.put(neighR, neighName); + ruleToId.put(neighR, neighId); } String pen = ""; @@ -280,7 +286,7 @@ public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { int nrOfMatches = antecedentNeighbors.get(neighR).size(); - sb.append(neighName).append(BaseRule.ARROW).append(currentName).append("[label=").append(nrOfMatches) + sb.append(q(neighId)).append("->").append(q(currentId)).append("[label=").append(nrOfMatches) .append(" ").append(pen).append("]").append("\n"); } @@ -293,8 +299,12 @@ public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { + (urlOnly ? "" : "\n" + sb.toString()); } + private String q(String aString) { + return "\"" + aString + "\""; + } + private String toStringRule(BaseRule neighR) { - return neighR.getAntecedent() + " -> " + neighR.getConsequent(); + return neighR.getAntecedent() + " → " + neighR.getConsequent(); } private String trimAtLength(String aString, int aLength) { @@ -342,15 +352,17 @@ private String generateName(BaseRule r) { String name = r.getName(); MessageDigest digest; - byte[] encodedhash = new byte[0]; + String encodedhash = "unknown"; try { digest = MessageDigest.getInstance("SHA-256"); - encodedhash = digest.digest(name.getBytes(StandardCharsets.UTF_8)); + +// encodedhash = digest.digest(name.getBytes(StandardCharsets.UTF_8)); + encodedhash = Integer.toHexString(System.identityHashCode(r)); } catch (NoSuchAlgorithmException e) { LOG.error("{}", e); } - - return "\"" + bytesToHex(encodedhash) + "\\n" + antecedent + "->\\n" + consequent + "\""; + // bytesToHex(...) + return "\"" + antecedent + "→\\n" + consequent + "\""; } private static String bytesToHex(byte[] hash) { diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java index 59d6c22c0..3e32f53c0 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java @@ -20,7 +20,6 @@ import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.sparql.lang.arq.ParseException; import org.apache.jena.vocabulary.RDF; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/PruningTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/PruningTest.java index f2259047c..b37103fa1 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/PruningTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/PruningTest.java @@ -16,11 +16,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.knowledge.engine.reasoner.ProactiveRule; -import eu.knowledge.engine.reasoner.ReasonerPlan; -import eu.knowledge.engine.reasoner.Rule; -import eu.knowledge.engine.reasoner.SinkBindingSetHandler; -import eu.knowledge.engine.reasoner.TaskBoard; import eu.knowledge.engine.reasoner.api.Binding; import eu.knowledge.engine.reasoner.api.BindingSet; import eu.knowledge.engine.reasoner.api.TriplePattern; diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/ReasoningPlanTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/ReasoningPlanTest.java index 21cff1cf2..701849cda 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/ReasoningPlanTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/ReasoningPlanTest.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import eu.knowledge.engine.reasoner.BaseRule; @@ -16,9 +15,6 @@ import eu.knowledge.engine.reasoner.ReasonerPlan; import eu.knowledge.engine.reasoner.Rule; import eu.knowledge.engine.reasoner.TaskBoard; -import eu.knowledge.engine.reasoner.api.Binding; -import eu.knowledge.engine.reasoner.api.BindingSet; -import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.reasoner.rulestore.RuleStore; import eu.knowledge.engine.reasoner.util.DataBindingSetHandler; import eu.knowledge.engine.reasoner.util.Table; @@ -74,10 +70,9 @@ public void test() throws IOException, InterruptedException, ExecutionException store.getConsequentNeighbors(r); } - store.printGraphVizCode(null); - ReasonerPlan plan = new ReasonerPlan(store, rule); + store.printGraphVizCode(plan); // plan.optimize(); BindingSet aBindingSet = new BindingSet(); diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java index ddf1b83cc..14fee24f9 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsEnabled.java @@ -1,18 +1,13 @@ package eu.knowledge.engine.rest.api; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.util.Map; -import org.apache.jena.atlas.logging.Log; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java index 8f4a29c04..a681da211 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestAskWithGapsNotEnabled.java @@ -2,17 +2,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.util.Map; -import org.apache.jena.atlas.logging.Log; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestDomainKnowledge.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestDomainKnowledge.java index e31354a76..f0c2d5c24 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestDomainKnowledge.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestDomainKnowledge.java @@ -1,14 +1,11 @@ package eu.knowledge.engine.rest.api; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.io.IOException; import java.net.URL; import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java index ea135efc1..ba0b2c553 100644 --- a/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java +++ b/smart-connector-rest-dist/src/test/java/eu/knowledge/engine/rest/api/TestRegisterKnowledgeInteraction.java @@ -6,7 +6,6 @@ import java.net.URL; import java.util.Map; -import org.apache.jena.atlas.logging.Log; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest2.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest2.java new file mode 100644 index 000000000..a235f5dd3 --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/NotDesignedToWorkTogetherTest2.java @@ -0,0 +1,154 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; + +/** + * This test tries to illustrate forward compatibility. We instantiate an + * App KB and a Lamp1 KB that exchange data about the on/off state of the lamp. + * When a new Lamp2 KB is introduced that does not support on/off because it is + * dimmable, we should how the reasoner+domain knowledge combination is able to + * make the app work with the new dimmable type of lamp although it was not + * designed to work with those types. + * + * This second version of this unit test is meant to test specific behaviour of + * the reasoner where a full domain rule does is not get activated, but there is + * still enough data to apply its consequent neighbor. + * + * @author nouwtb + * + */ +public class NotDesignedToWorkTogetherTest2 { + + private static final Logger LOG = LoggerFactory.getLogger(NotDesignedToWorkTogetherTest2.class); + + private static KnowledgeNetwork kn = new KnowledgeNetwork(); + private KnowledgeBaseImpl appKb = new KnowledgeBaseImpl("AppKB"); + private KnowledgeBaseImpl lamp1Kb = new KnowledgeBaseImpl("Lamp1KB"); + private KnowledgeBaseImpl lamp2Kb = new KnowledgeBaseImpl("Lamp2KB"); + private PrefixMapping prefixes = new PrefixMappingMem().setNsPrefix("ex", "http://example.org/") + .setNsPrefix("time", "https://www.w3.org/TR/owl-time/") + .setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + + @Test + public void test() throws InterruptedException { + + kn.addKB(appKb); + kn.addKB(lamp1Kb); + kn.addKB(lamp2Kb); + + final CountDownLatch latch = new CountDownLatch(2); + + GraphPattern lampGP = new GraphPattern(prefixes, "?l rdf:type ex:OnOffLamp .", "?l ex:isOn ?o ."); + PostKnowledgeInteraction appKbPost = new PostKnowledgeInteraction(new CommunicativeAct(), lampGP, null); + appKb.register(appKbPost); + + HashSet rule1ant = new HashSet<>(Arrays.asList( + new TriplePattern( + "?s "), + new TriplePattern( + "?s \"true\"^^"))); + HashSet rule1con = new HashSet<>(Arrays.asList( + new TriplePattern( + "?s "), + new TriplePattern("?s \"100\""))); + + // to test whether the reasoner of this example still works, we slightly modify + // a domain rule where it does not produce a binding set. This should not cause + // this unit test to fail, because the other domain rule is all we need. + HashSet rule2ant = new HashSet<>(Arrays.asList( + new TriplePattern( + " "), + new TriplePattern( + " \"false\"^^"))); + HashSet rule2con = new HashSet<>(Arrays.asList( + new TriplePattern( + " "), + new TriplePattern(" \"0\""))); + + Rule rule1 = new Rule(rule1ant, rule1con); + Rule rule2 = new Rule(rule2ant, rule2con); + appKb.setDomainKnowledge(new HashSet<>(Arrays.asList(rule1, rule2))); + + ReactKnowledgeInteraction lamp1KbReact = new ReactKnowledgeInteraction(new CommunicativeAct(), lampGP, null); + lamp1Kb.register(lamp1KbReact, (ReactHandler) (aKI, aReactInfo) -> { + + Iterator iterator = aReactInfo.getArgumentBindings().iterator(); + while (iterator.hasNext()) { + Binding b = iterator.next(); + if (b.containsKey("l") && b.get("l").equals("")) { + LOG.info("Turned lamp1 '{}'", b.get("o")); + latch.countDown(); + } + } + return new BindingSet(); + }); + + GraphPattern lamp2GP = new GraphPattern(prefixes, "?l rdf:type .", + "?l ?b ."); + ReactKnowledgeInteraction lamp2KbReact = new ReactKnowledgeInteraction(new CommunicativeAct(), lamp2GP, null); + lamp2Kb.register(lamp2KbReact, (ReactHandler) (aKI, aReactInfo) -> { + + Iterator iterator = aReactInfo.getArgumentBindings().iterator(); + while (iterator.hasNext()) { + Binding b = iterator.next(); + if (b.containsKey("l") && b.get("l").equals("")) { + LOG.info("Turned lamp2 '{}'", b.get("b")); + latch.countDown(); + } + } + + return new BindingSet(); + }); + + kn.sync(); + + BindingSet argument = new BindingSet(); + Binding binding = new Binding(); + binding.put("l", ""); + binding.put("o", "\"true\"^^"); + argument.add(binding); + Binding binding2 = new Binding(); + binding2.put("l", ""); + binding2.put("o", "\"true\"^^"); + argument.add(binding2); + PostPlan pp = appKb.planPost(appKbPost, new RecipientSelector()); + + pp.getReasonerPlan().getStore().printGraphVizCode(pp.getReasonerPlan()); + pp.execute(argument); + + boolean allTouched = latch.await(3000, TimeUnit.MILLISECONDS); + + assertTrue(allTouched); + + } + + @AfterAll + public static void close() { + LOG.info("Clean up: {}", NotDesignedToWorkTogetherTest2.class.getSimpleName()); + try { + kn.stop().get(); + } catch (InterruptedException | ExecutionException e) { + LOG.error("Stopping the Knowledge Network should succeed: {}", e); + } + } + +}