From 24da67147e7af58dc8ca6c83e125f32dce9f9961 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 27 Jun 2024 13:02:19 +0200 Subject: [PATCH 01/18] First steps in creating new graph pattern matching algorithm. --- .../knowledge/engine/reasoner/BaseRule.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java index a5ff2c741..68b5ebae2 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import org.apache.jena.sparql.core.Var; import org.slf4j.Logger; @@ -433,4 +434,56 @@ public boolean equals(Object obj) { return true; } + /** + * + * + * + * @param aGraphPattern The graph pattern for which we want to find + * matches. + * @param otherGraphPatterns The other graph patterns that we want to check + * whether and how they match to + * {@code aGraphPattern}. + * @param antecedent Whether + * @return A set of matches that all contribute to some full matche. + */ + + /** + * This method finds matches of {@code otherGraphPatterns} on + * {@code aGraphPattern} that are part of a full match of {@code aGraphPattern}. + * + * @param aRule + * @param otherRules + * @param antecedent + * @return + */ + public static Map> getFullMatches(BaseRule aRule, Set otherRules, boolean antecedent) { + + /* + * we use a list instead of a set for performance reasons. The list does not + * call Match#equals(...) method often everytime we add an entry. The algorithm + * makes sure matches that are added do not already exist. + */ + List allMatches = new ArrayList(); + + + + + // TODO first find all triples in the otherGraphPatterns that match each triple + // in aGraphPattern + + // TODO + + return convertToMap(allMatches); + } + + private static Map> convertToMap(List matches) { + return matches.stream().collect(Collectors.>toMap( + ruleMatch -> ruleMatch.rule, ruleMatch -> new HashSet(ruleMatch.matches))); + } + + private static class RuleMatchCombi { + public BaseRule rule; + public List matches; + } + } From 7db14d7d19858f3856547b07ff9ee69a85b34d68 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 27 Jun 2024 13:59:41 +0200 Subject: [PATCH 02/18] Trying the previous implementation of towards full match. --- .../knowledge/engine/reasoner/BaseRule.java | 249 ++++++++++++++++++ .../engine/reasoner/rulestore/RuleStore.java | 81 ++++-- .../engine/reasoner/MinimalTest.java | 2 +- 3 files changed, 313 insertions(+), 19 deletions(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java index 68b5ebae2..cabe034e1 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -434,6 +434,255 @@ public boolean equals(Object obj) { return true; } + + + // old implementation of matching towards full match + + /** + * Try if we can match with better heuristics: try to work towards full matches + * of the graph pattern using all the rules at once. This way, hopefully, a lot + * of matches can already be discarded and do not slow down the process further + * down the line. + * + * How do we know whether we should match the antecedent or consequent of those + * rules? + * + * @param aFirstPattern + * @param allRules + * @param aMatchStrategy + * @return a set of mappings from a rule to a match. + */ + public static Set> findMatches(Set aFirstPattern, List allRules, + MatchStrategy aMatchStrategy, boolean useConsequent) { + + List> allMatches = new ArrayList<>(); + + Map>> matchesPerTriplePerRule = getMatchesPerTriplePerRule(aFirstPattern, + allRules, useConsequent); + + // if not every triple pattern can be matched, we stop the process if we require + // a full match. + if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) + && matchesPerTriplePerRule.keySet().size() < aFirstPattern.size()) + return new HashSet<>(); + + // now combine all found matches. + List> biggestRuleMatches = new ArrayList<>(); + List> smallerMatches = new ArrayList<>(); + Match mergedMatch = null; + List> toBeAddedToBiggestMatches = null, toBeAddedToSmallerMatches = null; + Set toBeDemotedMatchIndices = null; + + Iterator>>> matchesPerTripleIter = matchesPerTriplePerRule + .entrySet().iterator(); + + // always add all matches of first triple + if (matchesPerTripleIter.hasNext()) { + biggestRuleMatches.addAll(matchesPerTripleIter.next().getValue()); + } + + while (matchesPerTripleIter.hasNext()) { + + Set> matchesForCurrentTriple = matchesPerTripleIter.next().getValue(); + + // keep a set of new/removed matches, so we can add/remove them at the end of + // this loop + + assert matchesForCurrentTriple != null; + + toBeAddedToBiggestMatches = new ArrayList<>(); + toBeAddedToSmallerMatches = new ArrayList<>(); + toBeDemotedMatchIndices = new HashSet<>(); + for (Map matchForCurrentTriple : matchesForCurrentTriple) { + // see if this next match can be merged with one of the existing biggest matches + for (Map.Entry m1entry : matchForCurrentTriple.entrySet()) { + Match m1 = m1entry.getValue(); + + // check if we need to merge with existing matches + boolean hasMerged = false; + // first check if m1 can be merged with any of the existing biggest matches. + for (int i = 0; i < biggestRuleMatches.size(); i++) { + + Map existingBiggestRuleMatch = biggestRuleMatches.get(i); + + Match m2 = existingBiggestRuleMatch.get(m1entry.getKey()); + + if (m2 != null) { + + mergedMatch = m2.merge(m1); + if (mergedMatch != null) { + hasMerged = true; + HashMap newRuleMatch = new HashMap<>(existingBiggestRuleMatch); + newRuleMatch.put(m1entry.getKey(), mergedMatch); + toBeAddedToBiggestMatches.add(newRuleMatch); + toBeDemotedMatchIndices.add(i); + } else if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + toBeDemotedMatchIndices.add(i); + } + } else { + toBeDemotedMatchIndices.add(i); + HashMap newRuleMatch = new HashMap<>(existingBiggestRuleMatch); + newRuleMatch.put(m1entry.getKey(), m1); + hasMerged = true; + if (!aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + toBeAddedToBiggestMatches.add(newRuleMatch); + } else { + toBeAddedToSmallerMatches.add(newRuleMatch); + } + } + } + + // then check if m1 can be merged with any of the existing smaller matches + if (!aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + + for (Map ruleMatch2 : smallerMatches) { + Match m2 = ruleMatch2.get(m1entry.getKey()); + + if (m2 != null) { + + mergedMatch = m2.merge(m1); + if (mergedMatch != null) { + + if (hasMerged) { + // add to smallerMatches and sometimes to biggestMatches. + if (isSubMatch2(ruleMatch2, toBeAddedToBiggestMatches)) { + // add to smaller matches + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), mergedMatch); + toBeAddedToSmallerMatches.add(newRuleMatch); + } else { + // add to biggest matches + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), mergedMatch); + toBeAddedToBiggestMatches.add(newRuleMatch); + } + } else { + // add to biggestMatches + hasMerged = true; + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), mergedMatch); + toBeAddedToBiggestMatches.add(newRuleMatch); + } + } + } else { + + if (hasMerged) { + + // TODO probably we need to check for isSubMatch2() here too? + // yes, if the current ruleMatch2 is a submatch of one of the + // toBeAddedToBiggestMatches + + if (isSubMatch2(ruleMatch2, toBeAddedToBiggestMatches)) { + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), m1); + toBeAddedToSmallerMatches.add(newRuleMatch); + } else { + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), m1); + toBeAddedToBiggestMatches.add(newRuleMatch); + } + } else { + HashMap newRuleMatch = new HashMap<>(ruleMatch2); + newRuleMatch.put(m1entry.getKey(), m1); + toBeAddedToBiggestMatches.add(newRuleMatch); + } + } + } + } + + if (!hasMerged && !aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + toBeAddedToBiggestMatches.add(matchForCurrentTriple); + } else { + toBeAddedToSmallerMatches.add(matchForCurrentTriple); + } + } + } + // remove all toBeDemotedMatches from the biggestMatches and add them to the + // smallerMatches. + + List sortedList = new ArrayList<>(toBeDemotedMatchIndices); + Collections.sort(sortedList, Collections.reverseOrder()); + for (int i : sortedList) { + smallerMatches.add(biggestRuleMatches.get(i)); + biggestRuleMatches.remove(i); + } + + // add all toBeAddedMatches + biggestRuleMatches.addAll(toBeAddedToBiggestMatches); + smallerMatches.addAll(toBeAddedToSmallerMatches); + + long innerEnd = System.currentTimeMillis(); + toBeAddedToBiggestMatches = null; + toBeDemotedMatchIndices = null; + toBeAddedToSmallerMatches = null; + + } + if (aMatchStrategy.equals(MatchStrategy.FIND_ALL_MATCHES)) { + allMatches.addAll(biggestRuleMatches); + allMatches.addAll(smallerMatches); + } else if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_BIGGEST_MATCHES) + || aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + allMatches.addAll(biggestRuleMatches); + } + + return new HashSet<>(allMatches); + + } + + + + private static boolean isSubMatch2(Map aRuleMatch, List> toBeAddedToBiggestMatches) { + + for (Map ruleMatch : toBeAddedToBiggestMatches) { + boolean foundAll = true; + for (Map.Entry rm : aRuleMatch.entrySet()) { + + if (!ruleMatch.containsKey(rm.getKey()) || !ruleMatch.get(rm.getKey()).isSubMatch(rm.getValue())) { + foundAll = false; + break; + } + } + if (foundAll) + return true; + } + return false; + } + + public static Map>> getMatchesPerTriplePerRule(Set aFirstPattern, + List allRules, boolean useConsequent) { + Map>> matchesPerRule = new HashMap<>(); + + for (BaseRule r : allRules) { + // first find all triples in the consequent that match each triple in the + // antecedent + List foundMatches; + for (TriplePattern tripleP : aFirstPattern) { + // find all possible matches of the current antecedent triple in the consequent + if (useConsequent ? !r.consequent.isEmpty() : !r.antecedent.isEmpty()) { + foundMatches = findMatches(tripleP, useConsequent ? r.consequent : r.antecedent); + if (!foundMatches.isEmpty()) { + + Set> ruleMatches = matchesPerRule.get(tripleP); + if (ruleMatches == null) { + ruleMatches = new HashSet<>(); + matchesPerRule.put(tripleP, ruleMatches); + } + for (Match m : foundMatches) { + HashMap hashMap = new HashMap(); + hashMap.put(r, m); + ruleMatches.add(hashMap); + } + } + } + } + } + return matchesPerRule; + } + + + //new implementation of matching towards full match + + /** * * 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 ef239b05e..5f096dd6b 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 @@ -4,7 +4,9 @@ package eu.knowledge.engine.reasoner.rulestore; import java.net.URI; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -88,21 +90,47 @@ public Map> getAntecedentNeighbors(BaseRule aRule, MatchStr assert aRuleNode != null; - for (BaseRule someRule : this.getRules()) { - MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); - if (!someRule.getConsequent().isEmpty() - && !aRuleNode.getAntecedentNeighbors(aStrategy).containsKey(someRule)) { - Set someMatches = aRule.antecedentMatches(someRule.getConsequent(), aStrategy); - if (!someMatches.isEmpty()) { - aRuleNode.setAntecedentNeighbor(someRule, someMatches, aStrategy); - someRuleNode.setConsequentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); - } + Set> mappings = BaseRule.findMatches(aRule.getAntecedent(), + new ArrayList(this.getRules()), aStrategy, true); + + Map> newMapping = new HashMap<>(); + for (Map mapping : mappings) { + for (Map.Entry entry : mapping.entrySet()) { + addToNewMapping(newMapping, entry); } } + + for (Map.Entry> entry : newMapping.entrySet()) { + aRuleNode.setAntecedentNeighbor(entry.getKey(), entry.getValue(), aStrategy); + this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, Match.invertAll(entry.getValue()), aStrategy); + } + +// old code +// for (BaseRule someRule : this.getRules()) { +// MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); +// if (!someRule.getConsequent().isEmpty() +// && !aRuleNode.getAntecedentNeighbors(aStrategy).containsKey(someRule)) { +// Set someMatches = aRule.antecedentMatches(someRule.getConsequent(), aStrategy); +// if (!someMatches.isEmpty()) { +// aRuleNode.setAntecedentNeighbor(someRule, someMatches, aStrategy); +// someRuleNode.setConsequentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); +// } +// +// } +// } return aRuleNode.getAntecedentNeighbors(aStrategy); } + public void addToNewMapping(Map> newMapping, Map.Entry entryToAdd) { + // check if rule already has entry in newMapping + if (!newMapping.containsKey(entryToAdd.getKey())) { + newMapping.put(entryToAdd.getKey(), new HashSet<>()); + } + Set matches = newMapping.get(entryToAdd.getKey()); + matches.add(entryToAdd.getValue()); + } + /** * @see #getConsequentNeighbors(BaseRule, MatchStrategy) */ @@ -126,18 +154,35 @@ public Map> getConsequentNeighbors(BaseRule aRule, MatchStr assert aRuleNode != null; - for (BaseRule someRule : this.getRules()) { - MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); - if (!someRule.getAntecedent().isEmpty() - && !aRuleNode.getConsequentNeighbors(aStrategy).containsKey(someRule)) { - Set someMatches = aRule.consequentMatches(someRule.getAntecedent(), aStrategy); - if (!someMatches.isEmpty()) { - aRuleNode.setConsequentNeighbor(someRule, someMatches, aStrategy); - someRuleNode.setAntecedentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); - } + Set> mappings = BaseRule.findMatches(aRule.getConsequent(), + new ArrayList(this.getRules()), aStrategy, false); + Map> newMapping = new HashMap<>(); + + for (Map mapping : mappings) { + for (Map.Entry entry : mapping.entrySet()) { + addToNewMapping(newMapping, entry); } } + + for (Map.Entry> entry : newMapping.entrySet()) { + aRuleNode.setConsequentNeighbor(entry.getKey(), entry.getValue(), aStrategy); + this.ruleToRuleNode.get(entry.getKey()).setAntecedentNeighbor(aRule, Match.invertAll(entry.getValue()), aStrategy); + } + +// old code +// for (BaseRule someRule : this.getRules()) { +// MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); +// if (!someRule.getAntecedent().isEmpty() +// && !aRuleNode.getConsequentNeighbors(aStrategy).containsKey(someRule)) { +// Set someMatches = aRule.consequentMatches(someRule.getAntecedent(), aStrategy); +// if (!someMatches.isEmpty()) { +// aRuleNode.setConsequentNeighbor(someRule, someMatches, aStrategy); +// someRuleNode.setAntecedentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); +// } +// +// } +// } return aRuleNode.getConsequentNeighbors(aStrategy); } diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/MinimalTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/MinimalTest.java index 7422697dc..84585bbc4 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/MinimalTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/MinimalTest.java @@ -59,7 +59,7 @@ public void testConverter() throws InterruptedException, ExecutionException { // Start reasoning ReasonerPlan root = new ReasonerPlan(this.store, startRule); - System.out.println(root); + this.store.printGraphVizCode(root); BindingSet bs = new BindingSet(); Binding binding2 = new Binding(); From a015c3818a9afdb99c2fd81ee629805ee2ff3d44 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 27 Jun 2024 14:35:23 +0200 Subject: [PATCH 03/18] The matches should be inversed for them to work. Look at this later, because is this how we want it? --- .../knowledge/engine/reasoner/rulestore/RuleStore.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 5f096dd6b..dd49fdffe 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 @@ -102,8 +102,8 @@ public Map> getAntecedentNeighbors(BaseRule aRule, MatchStr } for (Map.Entry> entry : newMapping.entrySet()) { - aRuleNode.setAntecedentNeighbor(entry.getKey(), entry.getValue(), aStrategy); - this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, Match.invertAll(entry.getValue()), aStrategy); + aRuleNode.setAntecedentNeighbor(entry.getKey(), Match.invertAll(entry.getValue()), aStrategy); + this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, entry.getValue(), aStrategy); } // old code @@ -166,10 +166,10 @@ public Map> getConsequentNeighbors(BaseRule aRule, MatchStr } for (Map.Entry> entry : newMapping.entrySet()) { - aRuleNode.setConsequentNeighbor(entry.getKey(), entry.getValue(), aStrategy); - this.ruleToRuleNode.get(entry.getKey()).setAntecedentNeighbor(aRule, Match.invertAll(entry.getValue()), aStrategy); + aRuleNode.setConsequentNeighbor(entry.getKey(), Match.invertAll(entry.getValue()), aStrategy); + this.ruleToRuleNode.get(entry.getKey()).setAntecedentNeighbor(aRule, entry.getValue(), aStrategy); } - + // old code // for (BaseRule someRule : this.getRules()) { // MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); From 14dad1dc9b77eec53277510d5b698046de5c44f5 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 27 Jun 2024 14:43:10 +0200 Subject: [PATCH 04/18] Always use full match, because we match against multiple rules at once. --- .../engine/smartconnector/impl/ReasonerProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java index 973b7d40a..561539030 100644 --- a/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java +++ b/smart-connector/src/main/java/eu/knowledge/engine/smartconnector/impl/ReasonerProcessor.java @@ -144,7 +144,7 @@ public void planAskInteraction(MyKnowledgeInteractionInfo aAKI) { new HashSet<>()); this.store.addRule(aRule); this.reasonerPlan = new ReasonerPlan(this.store, aRule, - ki.fullMatchOnly() ? MatchStrategy.FIND_ONLY_FULL_MATCHES : MatchStrategy.FIND_ALL_MATCHES); + ki.fullMatchOnly() ? MatchStrategy.FIND_ONLY_FULL_MATCHES : MatchStrategy.FIND_ONLY_FULL_MATCHES); } else { LOG.warn("Type should be Ask, not {}", this.myKnowledgeInteraction.getType()); this.finalBindingSetFuture.complete(new eu.knowledge.engine.reasoner.api.BindingSet()); @@ -210,7 +210,7 @@ public void planPostInteraction(MyKnowledgeInteractionInfo aPKI) { store.addRule(aRule); this.reasonerPlan = new ReasonerPlan(this.store, aRule, - pki.fullMatchOnly() ? MatchStrategy.FIND_ONLY_FULL_MATCHES : MatchStrategy.FIND_ALL_MATCHES); + pki.fullMatchOnly() ? MatchStrategy.FIND_ONLY_FULL_MATCHES : MatchStrategy.FIND_ONLY_FULL_MATCHES); } else { LOG.warn("Type should be Post, not {}", this.myKnowledgeInteraction.getType()); From 81fcb078e7e31ac4501f145d6551ff24632305d2 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 2 Jul 2024 12:10:58 +0200 Subject: [PATCH 05/18] First version of new algorithm. Had to introduce new strategy that limits the full matching to a single rule. This was neccesary to make meta-knowledge-interactions work faster. Also had to remove some invert match code, which I found strange, but had to do to make it work. There are still quite some tests the fail, so those need to be fixed first. Starting with the looping test. --- .../knowledge/engine/reasoner/BaseRule.java | 420 ++++++++---------- .../engine/reasoner/ReasonerPlan.java | 24 +- .../engine/reasoner/rulestore/MatchNode.java | 4 +- .../engine/reasoner/rulestore/RuleStore.java | 65 +-- .../engine/reasoner/api/MatchTest.java | 77 ++++ .../impl/ReasonerProcessor.java | 4 +- .../smartconnector/api/TestAskAnswer5.java | 185 ++++++++ 7 files changed, 469 insertions(+), 310 deletions(-) create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswer5.java diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java index cabe034e1..fd4085dc0 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import org.apache.jena.sparql.core.Var; import org.slf4j.Logger; @@ -33,7 +32,7 @@ public class BaseRule { public static final String ARROW = "->"; public static enum MatchStrategy { - FIND_ALL_MATCHES, FIND_ONLY_BIGGEST_MATCHES, FIND_ONLY_FULL_MATCHES + FIND_ALL_MATCHES, FIND_ONLY_BIGGEST_MATCHES, FIND_ONLY_FULL_MATCHES, FIND_ONLY_FULL_NON_COMBI_MATCHES } public static class TrivialBindingSetHandler implements TransformBindingSetHandler { @@ -434,305 +433,240 @@ public boolean equals(Object obj) { return true; } - - - // old implementation of matching towards full match - + public static Map> getMatchesPerTriplePerRule(Set aFirstPattern, + List allRules, boolean useConsequent) { + + Map> matchesPerRule = new HashMap<>(); + + for (BaseRule r : allRules) { + // first find all triples in the consequent that match each triple in the + // antecedent + List foundMatches; + for (TriplePattern tripleP : aFirstPattern) { + // find all possible matches of the current antecedent triple in the consequent + if (useConsequent ? !r.consequent.isEmpty() : !r.antecedent.isEmpty()) { + foundMatches = findMatches(tripleP, useConsequent ? r.consequent : r.antecedent); + if (!foundMatches.isEmpty()) { + + Set ruleMatches = matchesPerRule.get(tripleP); + if (ruleMatches == null) { + ruleMatches = new HashSet<>(); + matchesPerRule.put(tripleP, ruleMatches); + } + for (Match m : foundMatches) { + CombiMatch hashMap = new CombiMatch(); + hashMap.put(r, m); + ruleMatches.add(hashMap); + } + } + } + } + } + return matchesPerRule; + } + + // new implementation of matching towards full match + /** - * Try if we can match with better heuristics: try to work towards full matches - * of the graph pattern using all the rules at once. This way, hopefully, a lot - * of matches can already be discarded and do not slow down the process further - * down the line. - * - * How do we know whether we should match the antecedent or consequent of those - * rules? + * This method finds matches of {@code otherGraphPatterns} on + * {@code aGraphPattern} that are part of a full match of {@code aGraphPattern}. * - * @param aFirstPattern - * @param allRules - * @param aMatchStrategy - * @return a set of mappings from a rule to a match. + * @param aGraphPattern The graph pattern for which we want to find + * matches. + * @param otherGraphPatterns The other graph patterns that we want to check + * whether and how they match to + * {@code aGraphPattern}. + * @param antecedentOfTarget Whether to match the antecedent or consequent of + * the target rule. + * @return A set of matches that all contribute to some full matche. */ - public static Set> findMatches(Set aFirstPattern, List allRules, - MatchStrategy aMatchStrategy, boolean useConsequent) { + public static Map> getMatches(BaseRule aTargetRule, Set someCandidateRules, + boolean antecedentOfTarget, MatchStrategy aStrategy) { - List> allMatches = new ArrayList<>(); + Set targetGP = antecedentOfTarget ? aTargetRule.getAntecedent() : aTargetRule.getConsequent(); - Map>> matchesPerTriplePerRule = getMatchesPerTriplePerRule(aFirstPattern, - allRules, useConsequent); + /* + * we use a list instead of a set for performance reasons. The list does not + * call Match#equals(...) method often everytime we add an entry. The algorithm + * makes sure matches that are added do not already exist. + */ + List allMatches = new ArrayList(); + + // first find all triples in the someCandidateRules that match each triple + // in targetGP + Map> combiMatchesPerTriple = getMatchesPerTriplePerRule(targetGP, + new ArrayList<>(someCandidateRules), antecedentOfTarget); // if not every triple pattern can be matched, we stop the process if we require // a full match. - if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) - && matchesPerTriplePerRule.keySet().size() < aFirstPattern.size()) - return new HashSet<>(); - - // now combine all found matches. - List> biggestRuleMatches = new ArrayList<>(); - List> smallerMatches = new ArrayList<>(); - Match mergedMatch = null; - List> toBeAddedToBiggestMatches = null, toBeAddedToSmallerMatches = null; + if ((aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) + || aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_NON_COMBI_MATCHES)) + && combiMatchesPerTriple.keySet().size() < targetGP.size()) + return new HashMap<>(); + + List biggestMatches = new ArrayList<>(); + List smallerMatches = new ArrayList<>(); + List toBeAddedToBiggestMatches = null, toBeAddedToSmallerMatches = null; Set toBeDemotedMatchIndices = null; - Iterator>>> matchesPerTripleIter = matchesPerTriplePerRule - .entrySet().iterator(); + Iterator>> triplePatternMatchesIter = combiMatchesPerTriple.entrySet() + .iterator(); - // always add all matches of first triple - if (matchesPerTripleIter.hasNext()) { - biggestRuleMatches.addAll(matchesPerTripleIter.next().getValue()); + if (triplePatternMatchesIter.hasNext()) { + biggestMatches.addAll(triplePatternMatchesIter.next().getValue()); } - while (matchesPerTripleIter.hasNext()) { + int cnt = 1; + // iterate all (except the first) triple patterns of the target graph pattern. + while (triplePatternMatchesIter.hasNext()) { + Map.Entry> currentTriplePatternMatches = triplePatternMatchesIter.next(); + LOG.trace("{}/{} ({}): biggest: {}, smaller: {}", ++cnt, combiMatchesPerTriple.size(), + currentTriplePatternMatches.getValue().size(), biggestMatches.size(), smallerMatches.size()); - Set> matchesForCurrentTriple = matchesPerTripleIter.next().getValue(); - - // keep a set of new/removed matches, so we can add/remove them at the end of - // this loop - - assert matchesForCurrentTriple != null; + Set candidateCombiMatches = currentTriplePatternMatches.getValue(); toBeAddedToBiggestMatches = new ArrayList<>(); toBeAddedToSmallerMatches = new ArrayList<>(); toBeDemotedMatchIndices = new HashSet<>(); - for (Map matchForCurrentTriple : matchesForCurrentTriple) { - // see if this next match can be merged with one of the existing biggest matches - for (Map.Entry m1entry : matchForCurrentTriple.entrySet()) { - Match m1 = m1entry.getValue(); - - // check if we need to merge with existing matches - boolean hasMerged = false; - // first check if m1 can be merged with any of the existing biggest matches. - for (int i = 0; i < biggestRuleMatches.size(); i++) { - - Map existingBiggestRuleMatch = biggestRuleMatches.get(i); - Match m2 = existingBiggestRuleMatch.get(m1entry.getKey()); + // try to merge with biggest combi matches + for (int i = 0; i < biggestMatches.size(); i++) { + CombiMatch aBiggestMatch = biggestMatches.get(i); + // iterate all candidates for current triple pattern + for (CombiMatch candidateCombiMatch : candidateCombiMatches) { - if (m2 != null) { + // compare/combine combimatches. Can we do this in a separate method? Or do we + // need to pass on too much state information for this to make it worth it? + boolean allowCombi = !aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_NON_COMBI_MATCHES); + CombiMatch newCombiMatch = mergeCombiMatches(candidateCombiMatch, aBiggestMatch, allowCombi); - mergedMatch = m2.merge(m1); - if (mergedMatch != null) { - hasMerged = true; - HashMap newRuleMatch = new HashMap<>(existingBiggestRuleMatch); - newRuleMatch.put(m1entry.getKey(), mergedMatch); - toBeAddedToBiggestMatches.add(newRuleMatch); - toBeDemotedMatchIndices.add(i); - } else if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { - toBeDemotedMatchIndices.add(i); - } - } else { - toBeDemotedMatchIndices.add(i); - HashMap newRuleMatch = new HashMap<>(existingBiggestRuleMatch); - newRuleMatch.put(m1entry.getKey(), m1); - hasMerged = true; - if (!aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { - toBeAddedToBiggestMatches.add(newRuleMatch); - } else { - toBeAddedToSmallerMatches.add(newRuleMatch); - } - } + if (newCombiMatch != null) { + // successful merge add new biggest + toBeAddedToBiggestMatches.add(newCombiMatch); } + } - // then check if m1 can be merged with any of the existing smaller matches - if (!aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { - - for (Map ruleMatch2 : smallerMatches) { - Match m2 = ruleMatch2.get(m1entry.getKey()); - - if (m2 != null) { - - mergedMatch = m2.merge(m1); - if (mergedMatch != null) { - - if (hasMerged) { - // add to smallerMatches and sometimes to biggestMatches. - if (isSubMatch2(ruleMatch2, toBeAddedToBiggestMatches)) { - // add to smaller matches - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), mergedMatch); - toBeAddedToSmallerMatches.add(newRuleMatch); - } else { - // add to biggest matches - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), mergedMatch); - toBeAddedToBiggestMatches.add(newRuleMatch); - } - } else { - // add to biggestMatches - hasMerged = true; - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), mergedMatch); - toBeAddedToBiggestMatches.add(newRuleMatch); - } - } - } else { - - if (hasMerged) { - - // TODO probably we need to check for isSubMatch2() here too? - // yes, if the current ruleMatch2 is a submatch of one of the - // toBeAddedToBiggestMatches - - if (isSubMatch2(ruleMatch2, toBeAddedToBiggestMatches)) { - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), m1); - toBeAddedToSmallerMatches.add(newRuleMatch); - } else { - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), m1); - toBeAddedToBiggestMatches.add(newRuleMatch); - } - } else { - HashMap newRuleMatch = new HashMap<>(ruleMatch2); - newRuleMatch.put(m1entry.getKey(), m1); - toBeAddedToBiggestMatches.add(newRuleMatch); - } - } - } - } + toBeDemotedMatchIndices.add(i); - if (!hasMerged && !aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { - toBeAddedToBiggestMatches.add(matchForCurrentTriple); - } else { - toBeAddedToSmallerMatches.add(matchForCurrentTriple); - } - } } - // remove all toBeDemotedMatches from the biggestMatches and add them to the - // smallerMatches. + + // update all collections List sortedList = new ArrayList<>(toBeDemotedMatchIndices); Collections.sort(sortedList, Collections.reverseOrder()); for (int i : sortedList) { - smallerMatches.add(biggestRuleMatches.get(i)); - biggestRuleMatches.remove(i); + if (!fullOnly(aStrategy)) { + smallerMatches.add(biggestMatches.get(i)); + } + biggestMatches.remove(i); } // add all toBeAddedMatches - biggestRuleMatches.addAll(toBeAddedToBiggestMatches); + biggestMatches.addAll(toBeAddedToBiggestMatches); smallerMatches.addAll(toBeAddedToSmallerMatches); - long innerEnd = System.currentTimeMillis(); toBeAddedToBiggestMatches = null; toBeDemotedMatchIndices = null; toBeAddedToSmallerMatches = null; - } - if (aMatchStrategy.equals(MatchStrategy.FIND_ALL_MATCHES)) { - allMatches.addAll(biggestRuleMatches); + + if (aStrategy.equals(MatchStrategy.FIND_ALL_MATCHES)) { + allMatches.addAll(biggestMatches); allMatches.addAll(smallerMatches); - } else if (aMatchStrategy.equals(MatchStrategy.FIND_ONLY_BIGGEST_MATCHES) - || aMatchStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { - allMatches.addAll(biggestRuleMatches); + } else if (aStrategy.equals(MatchStrategy.FIND_ONLY_BIGGEST_MATCHES) || fullOnly(aStrategy)) { + allMatches.addAll(biggestMatches); } - return new HashSet<>(allMatches); - + return convertToMap(allMatches); } - - - - private static boolean isSubMatch2(Map aRuleMatch, List> toBeAddedToBiggestMatches) { - - for (Map ruleMatch : toBeAddedToBiggestMatches) { - boolean foundAll = true; - for (Map.Entry rm : aRuleMatch.entrySet()) { - - if (!ruleMatch.containsKey(rm.getKey()) || !ruleMatch.get(rm.getKey()).isSubMatch(rm.getValue())) { - foundAll = false; - break; - } - } - if (foundAll) - return true; - } - return false; + + private static boolean fullOnly(MatchStrategy aStrategy) { + return aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) + || aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_NON_COMBI_MATCHES); } - public static Map>> getMatchesPerTriplePerRule(Set aFirstPattern, - List allRules, boolean useConsequent) { - Map>> matchesPerRule = new HashMap<>(); + private static void printCombiMatchesPerTriple(Map> combiMatchesPerTriple) { + StringBuilder sb = new StringBuilder(); - for (BaseRule r : allRules) { - // first find all triples in the consequent that match each triple in the - // antecedent - List foundMatches; - for (TriplePattern tripleP : aFirstPattern) { - // find all possible matches of the current antecedent triple in the consequent - if (useConsequent ? !r.consequent.isEmpty() : !r.antecedent.isEmpty()) { - foundMatches = findMatches(tripleP, useConsequent ? r.consequent : r.antecedent); - if (!foundMatches.isEmpty()) { - - Set> ruleMatches = matchesPerRule.get(tripleP); - if (ruleMatches == null) { - ruleMatches = new HashSet<>(); - matchesPerRule.put(tripleP, ruleMatches); - } - for (Match m : foundMatches) { - HashMap hashMap = new HashMap(); - hashMap.put(r, m); - ruleMatches.add(hashMap); - } - } - } - } + int total = 1; + for (Set combiMatch : combiMatchesPerTriple.values()) { + total = total * combiMatch.size(); + sb.append(combiMatch.size()).append(" * "); } - return matchesPerRule; + + LOG.trace("{} = {}", total, sb.toString()); + } - - - //new implementation of matching towards full match - - - /** - * - * - * - * @param aGraphPattern The graph pattern for which we want to find - * matches. - * @param otherGraphPatterns The other graph patterns that we want to check - * whether and how they match to - * {@code aGraphPattern}. - * @param antecedent Whether - * @return A set of matches that all contribute to some full matche. - */ /** - * This method finds matches of {@code otherGraphPatterns} on - * {@code aGraphPattern} that are part of a full match of {@code aGraphPattern}. + * Tries to merge the singleton candidate combi match with the given biggest + * match. * - * @param aRule - * @param otherRules - * @param antecedent - * @return + * @param candidateCombiMatch The candidate combi match + * @param aBiggestCombiMatch The biggest match to merge with + * @return a new CombiMatch if merge is possible, {@code null} otherwise. */ - public static Map> getFullMatches(BaseRule aRule, Set otherRules, boolean antecedent) { + private static CombiMatch mergeCombiMatches(CombiMatch candidateCombiMatch, CombiMatch aBiggestCombiMatch, + boolean allowCombi) { - /* - * we use a list instead of a set for performance reasons. The list does not - * call Match#equals(...) method often everytime we add an entry. The algorithm - * makes sure matches that are added do not already exist. - */ - List allMatches = new ArrayList(); + assert candidateCombiMatch.keySet().size() == 1; - - - - // TODO first find all triples in the otherGraphPatterns that match each triple - // in aGraphPattern + Map.Entry entry = candidateCombiMatch.entrySet().iterator().next(); - // TODO + BaseRule candidateRule = entry.getKey(); + Match candidateMatch = entry.getValue(); - return convertToMap(allMatches); - } + if (aBiggestCombiMatch.containsKey(candidateRule)) { + // rule already present, try to merge matches. + Match biggestMatch = aBiggestCombiMatch.get(candidateRule); + Match newMatch = biggestMatch.merge(candidateMatch); + + if (newMatch != null) { + // merge successful + CombiMatch newCombiMatch = new CombiMatch(aBiggestCombiMatch); + newCombiMatch.put(candidateRule, newMatch); + return newCombiMatch; + } + } else if (allowCombi) { + // rule not yet present, add new entry for rule. + CombiMatch newCombiMatch = new CombiMatch(aBiggestCombiMatch); + newCombiMatch.put(candidateRule, candidateMatch); + return newCombiMatch; + } - private static Map> convertToMap(List matches) { - return matches.stream().collect(Collectors.>toMap( - ruleMatch -> ruleMatch.rule, ruleMatch -> new HashSet(ruleMatch.matches))); + return null; } - private static class RuleMatchCombi { - public BaseRule rule; - public List matches; + private static Map> convertToMap(List combiMatches) { + Map> matchesPerRule = new HashMap<>(); + for (CombiMatch combiMatch : combiMatches) { + for (Map.Entry ruleMatch : combiMatch.entrySet()) { + + // create if not already exists + if (!matchesPerRule.containsKey(ruleMatch.getKey())) { + matchesPerRule.put(ruleMatch.getKey(), new HashSet<>()); + } + assert matchesPerRule.containsKey(ruleMatch.getKey()); + matchesPerRule.get(ruleMatch.getKey()).add(ruleMatch.getValue()); + } + } + + return matchesPerRule; } + /** + * This class represents a single combi match which consists of one or more + * rules mapped to a single Match object. + */ + private static class CombiMatch extends HashMap { + private static final long serialVersionUID = 1L; + + public CombiMatch() { + super(); + } + + public CombiMatch(CombiMatch aBiggestCombiMatch) { + super(aBiggestCombiMatch); + } + } } 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 9dc0c726d..d49484db6 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java @@ -1,11 +1,11 @@ package eu.knowledge.engine.reasoner; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -130,7 +130,7 @@ public TaskBoard execute(BindingSet bindingSet) { assert current instanceof AntSide; ((AntSide) current).getAntecedentNeighbours().forEach((n, matches) -> { var translated = toBeFilterPropagated.translate(n.getRule().getConsequent(), - Match.invertAll(matches)); + matches); boolean itChanged = ((ConsSide) n).addFilterBindingSetInput(current, translated); if (itChanged) { changed.add(n); @@ -144,7 +144,7 @@ public TaskBoard execute(BindingSet bindingSet) { assert current instanceof ConsSide; ((ConsSide) current).getConsequentNeighbours().forEach((n, matches) -> { var translated = toBeResultPropagated.translate(n.getRule().getAntecedent(), - Match.invertAll(matches)); + matches); TripleVarBindingSet beforeBindingSet = n.getResultBindingSetInput(); boolean itChanged = ((AntSide) n).addResultBindingSetInput(current, translated); @@ -267,8 +267,8 @@ private RuleNode createOrGetReasonerNode(BaseRule aRule, BaseRule aParent) { boolean ourAntecedentFullyMatchesParentConsequent = false; if (aParent != null && this.store.getAntecedentNeighbors(aRule, this.strategy).containsKey(aParent)) { - ourAntecedentFullyMatchesParentConsequent = antecedentFullyMatchesConsequent(aRule.getAntecedent(), - aParent.getConsequent(), this.getMatchStrategy()); + ourAntecedentFullyMatchesParentConsequent = antecedentFullyMatchesConsequent(aRule, aParent, + this.strategy); } if (!ourAntecedentFullyMatchesParentConsequent) { @@ -312,20 +312,26 @@ private void scheduleOrDoTask(RuleNode current, TaskBoard taskBoard) { * that if the antecedent is a subset of the consequent this method also return * true. * - * @param consequent - * @param antecedent + * @param consequentRule + * @param antecedentRule * @return */ - private boolean antecedentFullyMatchesConsequent(Set antecedent, Set consequent, + private boolean antecedentFullyMatchesConsequent(BaseRule antecedentRule, BaseRule consequentRule, MatchStrategy aMatchStrategy) { + var antecedent = antecedentRule.getAntecedent(); + var consequent = consequentRule.getConsequent(); + assert !antecedent.isEmpty(); assert !consequent.isEmpty(); + if (antecedent.size() > consequent.size()) return false; - Set matches = BaseRule.matches(antecedent, consequent, aMatchStrategy); + Set matches = BaseRule + .getMatches(antecedentRule, new HashSet<>(Arrays.asList(consequentRule)), true, aMatchStrategy).values() + .iterator().next(); for (Match m : matches) { // check if there is a match that is full 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 00dc63a7e..4f9501df7 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 @@ -91,7 +91,7 @@ public void setAntecedentNeighbor(BaseRule aRule, Set someMatches) { public void setConsequentNeighbor(BaseRule aRule, Set someMatches, MatchStrategy aStrategy) { Map> neighbors; - if (aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + if (aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) || aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_NON_COMBI_MATCHES)) { neighbors = this.consequentNeighborsFull; } else if (aStrategy.equals(MatchStrategy.FIND_ONLY_BIGGEST_MATCHES)) { neighbors = this.consequentNeighborsBiggest; @@ -109,7 +109,7 @@ public void setConsequentNeighbor(BaseRule aRule, Set someMatches, MatchS public void setAntecedentNeighbor(BaseRule aRule, Set someMatches, MatchStrategy aStrategy) { Map> neighbors; - if (aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES)) { + if (aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_MATCHES) || aStrategy.equals(MatchStrategy.FIND_ONLY_FULL_NON_COMBI_MATCHES)) { neighbors = this.antecedentNeighborsFull; } else if (aStrategy.equals(MatchStrategy.FIND_ONLY_BIGGEST_MATCHES)) { neighbors = this.antecedentNeighborsBiggest; 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 dd49fdffe..0e5d01151 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 @@ -74,7 +74,7 @@ public Set getRules() { * @see #getAntecedentNeighbors(BaseRule, MatchStrategy) */ public Map> getAntecedentNeighbors(BaseRule aRule) { - return this.getAntecedentNeighbors(aRule, MatchStrategy.FIND_ALL_MATCHES); + return this.getAntecedentNeighbors(aRule, MatchStrategy.FIND_ONLY_FULL_MATCHES); } /** @@ -90,36 +90,16 @@ public Map> getAntecedentNeighbors(BaseRule aRule, MatchStr assert aRuleNode != null; - Set> mappings = BaseRule.findMatches(aRule.getAntecedent(), - new ArrayList(this.getRules()), aStrategy, true); - - Map> newMapping = new HashMap<>(); - - for (Map mapping : mappings) { - for (Map.Entry entry : mapping.entrySet()) { - addToNewMapping(newMapping, entry); - } - } - + Map> newMapping = BaseRule.getMatches(aRule, this.getRules(), true, aStrategy); + for (Map.Entry> entry : newMapping.entrySet()) { aRuleNode.setAntecedentNeighbor(entry.getKey(), Match.invertAll(entry.getValue()), aStrategy); - this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, entry.getValue(), aStrategy); + this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, entry.getValue(), + aStrategy); } -// old code -// for (BaseRule someRule : this.getRules()) { -// MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); -// if (!someRule.getConsequent().isEmpty() -// && !aRuleNode.getAntecedentNeighbors(aStrategy).containsKey(someRule)) { -// Set someMatches = aRule.antecedentMatches(someRule.getConsequent(), aStrategy); -// if (!someMatches.isEmpty()) { -// aRuleNode.setAntecedentNeighbor(someRule, someMatches, aStrategy); -// someRuleNode.setConsequentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); -// } -// -// } -// } - return aRuleNode.getAntecedentNeighbors(aStrategy); + return newMapping; + } public void addToNewMapping(Map> newMapping, Map.Entry entryToAdd) { @@ -135,7 +115,7 @@ public void addToNewMapping(Map> newMapping, Map.Entry> getConsequentNeighbors(BaseRule aRule) { - return this.getConsequentNeighbors(aRule, MatchStrategy.FIND_ALL_MATCHES); + return this.getConsequentNeighbors(aRule, MatchStrategy.FIND_ONLY_FULL_MATCHES); } @@ -154,37 +134,14 @@ public Map> getConsequentNeighbors(BaseRule aRule, MatchStr assert aRuleNode != null; - Set> mappings = BaseRule.findMatches(aRule.getConsequent(), - new ArrayList(this.getRules()), aStrategy, false); - - Map> newMapping = new HashMap<>(); - - for (Map mapping : mappings) { - for (Map.Entry entry : mapping.entrySet()) { - addToNewMapping(newMapping, entry); - } - } + Map> newMapping = BaseRule.getMatches(aRule, this.getRules(), false, aStrategy); for (Map.Entry> entry : newMapping.entrySet()) { aRuleNode.setConsequentNeighbor(entry.getKey(), Match.invertAll(entry.getValue()), aStrategy); this.ruleToRuleNode.get(entry.getKey()).setAntecedentNeighbor(aRule, entry.getValue(), aStrategy); } -// old code -// for (BaseRule someRule : this.getRules()) { -// MatchNode someRuleNode = this.ruleToRuleNode.get(someRule); -// if (!someRule.getAntecedent().isEmpty() -// && !aRuleNode.getConsequentNeighbors(aStrategy).containsKey(someRule)) { -// Set someMatches = aRule.consequentMatches(someRule.getAntecedent(), aStrategy); -// if (!someMatches.isEmpty()) { -// aRuleNode.setConsequentNeighbor(someRule, someMatches, aStrategy); -// someRuleNode.setAntecedentNeighbor(aRule, Match.invertAll(someMatches), aStrategy); -// } -// -// } -// } - return aRuleNode.getConsequentNeighbors(aStrategy); - + return newMapping; } /** @@ -207,7 +164,7 @@ public void printGraphVizCode(ReasonerPlan aPlan) { String width = "2"; StringBuilder sb = new StringBuilder(); - sb.append("Visualize on website: http://viz-js.com/\n"); + sb.append("Visualize on website: https://dreampuf.github.io/GraphvizOnline/\n"); sb.append("digraph {").append("\n"); Map ruleToName = new HashMap<>(); diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/MatchTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/MatchTest.java index c9314e909..0e9080d5d 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/MatchTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/api/MatchTest.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.apache.jena.sparql.sse.SSE; import org.junit.jupiter.api.Disabled; @@ -19,9 +20,12 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; +import eu.knowledge.engine.reasoner.BaseRule; import eu.knowledge.engine.reasoner.BaseRule.MatchStrategy; import eu.knowledge.engine.reasoner.Match; +import eu.knowledge.engine.reasoner.ProactiveRule; import eu.knowledge.engine.reasoner.Rule; +import eu.knowledge.engine.reasoner.SinkBindingSetHandler; @TestInstance(Lifecycle.PER_CLASS) public class MatchTest { @@ -583,4 +587,77 @@ public void testPloutosGPMatcher() { System.out.println("Size: " + findMatchesWithConsequent.size()); } + + @Test + public void testPloutosGPMatcher2() { + + String gp = """ + ?operation . + ?operation ?output . + ?operation ?parcel . + ?parcel ?crop . + ?crop ?cropType . + ?cropType