From 455f83a0330772abf3a1cf230fc04400a9e50ebc Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 10 Jun 2025 15:40:59 +0200 Subject: [PATCH 1/9] Create unit tests for combi matches. Done: - remove unnecessary imports - fix bug in BaseRule - create even simpeler transitivity unit test - create unit test for large binding set --- .../knowledge/engine/reasoner/BaseRule.java | 4 +- .../engine/reasoner/BackwardTest.java | 9 - .../reasoner/SimpleTransitivityTest.java | 84 +++++++++ .../api/TestAskAnswerLargeBindingSets.java | 165 ++++++++++++++++++ 4 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 reasoner/src/test/java/eu/knowledge/engine/reasoner/SimpleTransitivityTest.java create mode 100644 smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.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 07bec683d..77244a86d 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -597,7 +597,7 @@ private static CombiMatch mergeCombiMatches(CombiMatch candidateCombiMatch, Comb CombiMatch newCombiMatch = new CombiMatch(aBiggestCombiMatch); Set newBiggestMatch = new HashSet<>(biggestMatch); - // we merge it with one of the avaialble matches (does that work?) + // we merge it with one of the available matches (does that work?) for (Match m : biggestMatch) { newMatch = m.merge(candidateMatch); if (newMatch != null) { @@ -635,7 +635,7 @@ private static boolean doesSameCandidateTripleMapToDifferentTriple(Match candida for (Match biggestMatch : biggestMatches) { var biggestMatchMP = biggestMatch.getMatchingPatterns(); for (Map.Entry entry : biggestMatchMP.entrySet()) { - if (entry.getValue().equals(candidateMatchMP.getValue())) { + if (!entry.getValue().equals(candidateMatchMP.getValue())) { return true; } } diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/BackwardTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/BackwardTest.java index 550123fff..910ffc613 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/BackwardTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/BackwardTest.java @@ -12,15 +12,11 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.apache.jena.graph.Node; -import org.apache.jena.graph.Triple; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Statement; import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.sparql.graph.PrefixMappingZero; import org.apache.jena.sparql.lang.arq.ParseException; -import org.apache.jena.sparql.sse.SSE; import org.apache.jena.sparql.util.FmtUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -29,11 +25,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.TaskBoard; -import eu.knowledge.engine.reasoner.TransformBindingSetHandler; 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/SimpleTransitivityTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/SimpleTransitivityTest.java new file mode 100644 index 000000000..48bc87f85 --- /dev/null +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/SimpleTransitivityTest.java @@ -0,0 +1,84 @@ +package eu.knowledge.engine.reasoner; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +import eu.knowledge.engine.reasoner.BaseRule.MatchFlag; +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; + +@TestInstance(Lifecycle.PER_CLASS) +public class SimpleTransitivityTest { + + private RuleStore store; + + @BeforeAll + public void init() { + store = new RuleStore(); + + // transitivity rule + Set antecedent = new HashSet<>(); + antecedent.add(new TriplePattern("?x ?y")); + antecedent.add(new TriplePattern("?y ?z")); + + Set consequent = new HashSet<>(); + consequent.add(new TriplePattern("?x ?z")); + + Rule transitivity = new Rule(antecedent, consequent); + + store.addRule(transitivity); + + // data rule + DataBindingSetHandler aBindingSetHandler = new DataBindingSetHandler( + new Table(new String[] { "a", "b" }, new String[] { + //@formatter:off + ",", + ",", + //@formatter:on + })); + + Rule rule = new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(new TriplePattern("?a ?b"))), + aBindingSetHandler); + + store.addRule(rule); + } + + @Test + public void test() throws InterruptedException, ExecutionException { + Set aGoal = new HashSet<>(); + aGoal.add(new TriplePattern("?x ?y")); + ProactiveRule startRule = new ProactiveRule(aGoal, new HashSet<>()); + store.addRule(startRule); + + ReasonerPlan plan = new ReasonerPlan(store, startRule, EnumSet.of(MatchFlag.ONLY_BIGGEST)); + + store.printGraphVizCode(plan); + + TaskBoard tb; + BindingSet bindingSet = new BindingSet(); + bindingSet.add(new Binding()); + + while ((tb = plan.execute(bindingSet)).hasTasks()) { + tb.executeScheduledTasks().get(); + } + + BindingSet result = plan.getResults(); + + // it should be , and , and (derived) ,. + assertEquals(3, result.size()); + } +} diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java new file mode 100644 index 000000000..9cd39b19e --- /dev/null +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java @@ -0,0 +1,165 @@ +package eu.knowledge.engine.smartconnector.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import org.apache.jena.shared.PrefixMapping; +import org.apache.jena.sparql.graph.PrefixMappingMem; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.knowledge.engine.smartconnector.util.KnowledgeBaseImpl; +import eu.knowledge.engine.smartconnector.util.KnowledgeNetwork; + +public class TestAskAnswerLargeBindingSets { + + private static final Logger LOG = LoggerFactory.getLogger(TestAskAnswerLargeBindingSets.class); + + private static KnowledgeBaseImpl kb1; + private static KnowledgeBaseImpl kb2; + private static KnowledgeBaseImpl kb3; + + private static BindingSet kb1BS; + private static BindingSet kb2BS; + + private static char[] chars = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }; + + @BeforeAll + public static void setup() throws InterruptedException, BrokenBarrierException, TimeoutException { + + kb1BS = new BindingSet(); + + Binding b; + for (int i = 0; i < 350; i++) { + b = new Binding(); + for (char c : chars) + b.put(Character.toString(c), ""); + kb1BS.add(b); + } + + kb2BS = new BindingSet(); + + for (int i = 350; i < 700; i++) { + b = new Binding(); + for (char c : chars) + b.put(Character.toString(c), ""); + kb2BS.add(b); + } + + } + + @Test + public void testAskAnswer() throws InterruptedException { + + PrefixMappingMem prefixes = new PrefixMappingMem(); + prefixes.setNsPrefixes(PrefixMapping.Standard); + prefixes.setNsPrefix("ex", "https://www.tno.nl/example/"); + + var kn = new KnowledgeNetwork(); + kb1 = new KnowledgeBaseImpl("kb1"); + kn.addKB(kb1); + kb2 = new KnowledgeBaseImpl("kb2"); + kn.addKB(kb2); + kb3 = new KnowledgeBaseImpl("kb3"); + kn.addKB(kb3); + + GraphPattern gp1 = new GraphPattern(prefixes, """ + ?a ?b . + ?a ?c . + ?a ?d . + ?a ?e . + ?e ?f . + ?e ?g . + """); + + AnswerKnowledgeInteraction aKI = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1); + kb1.register(aKI, (AnswerHandler) (anAKI, anAnswerExchangeInfo) -> { + assertTrue( + anAnswerExchangeInfo.getIncomingBindings().isEmpty() + || anAnswerExchangeInfo.getIncomingBindings().iterator().next().getVariables().isEmpty(), + "Should not have bindings in this binding set."); + + return kb1BS; + }); + + AnswerKnowledgeInteraction aKI3 = new AnswerKnowledgeInteraction(new CommunicativeAct(), gp1); + kb3.register(aKI3, (AnswerHandler) (anAKI, anAnswerExchangeInfo) -> { + assertTrue( + anAnswerExchangeInfo.getIncomingBindings().isEmpty() + || anAnswerExchangeInfo.getIncomingBindings().iterator().next().getVariables().isEmpty(), + "Should not have bindings in this binding set."); + + return kb2BS; + }); + + AskKnowledgeInteraction askKI = new AskKnowledgeInteraction(new CommunicativeAct(), gp1); + kb2.register(askKI); + LOG.info("Waiting until everyone is up-to-date!"); + kn.sync(); + LOG.info("Everyone is up-to-date!"); + + // start testing! + BindingSet bindings = null; + AskResult result = null; + try { + LOG.trace("Before ask."); + + try { + result = kb2.ask(askKI, new BindingSet()).get(10, TimeUnit.SECONDS); + } catch (TimeoutException te) { + fail("This ASK should take 10 seconds maximally."); + } + + bindings = result.getBindings(); + LOG.trace("After ask."); + + Set kbIds = result.getExchangeInfoPerKnowledgeBase().stream().map(AskExchangeInfo::getKnowledgeBaseId) + .collect(Collectors.toSet()); + + assertEquals(new HashSet(Arrays.asList(kb1.getKnowledgeBaseId(), kb3.getKnowledgeBaseId())), kbIds, + "The result should come from kb1, kb3 and not: " + kbIds); + + assertEquals(700, bindings.size()); + + } catch (InterruptedException | ExecutionException e) { + fail(); + } + } + + @AfterAll + public static void cleanup() { + LOG.info("Clean up: {}", TestAskAnswerLargeBindingSets.class.getSimpleName()); + if (kb1 != null) { + kb1.stop(); + } else { + fail("KB1 should not be null!"); + } + + if (kb2 != null) { + + kb2.stop(); + } else { + fail("KB2 should not be null!"); + } + + if (kb3 != null) { + kb3.stop(); + } else { + fail("KB3 should not be null!"); + } + } +} From 43e2013052d051f37ad416d81b989635a19a6861 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 12 Jun 2025 10:34:05 +0200 Subject: [PATCH 2/9] Added first version of CombiMatch merge method. --- .../knowledge/engine/reasoner/BaseRule.java | 2 +- .../engine/reasoner/ReasonerPlan.java | 55 +++++++++++++++++ .../reasoner/api/TripleVarBindingSet.java | 59 ++++++++++++++++++- 3 files changed, 113 insertions(+), 3 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 77244a86d..a2ebccd4c 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -670,7 +670,7 @@ private static Map> convertToMap(List combiMatc * matched to the same triple pattern. This is the case in transitivity * scenario's and we there support multiple match objects. */ - private static class CombiMatch extends HashMap> { + public static class CombiMatch extends HashMap> { private static final long serialVersionUID = 1L; public CombiMatch() { 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 d4fcb64fe..a3da37bb3 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java @@ -6,12 +6,14 @@ 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.BaseRule.MatchFlag; import eu.knowledge.engine.reasoner.api.Binding; import eu.knowledge.engine.reasoner.api.BindingSet; @@ -171,6 +173,59 @@ public TaskBoard execute(BindingSet bindingSet) { return taskBoard; } + /** + * Translates the given TripleVarBindingSets from neighbors to a single + * TripleVarBindingSet using the given combi matches. Because the combi matches + * are very specific to how they should be formed, using this information should + * speed up the binding set merging process considerably. In the previous (old) + * version, the binding sets were all being merge in every possible way to make + * sure all possible bindings were present (also for cases like transitivity). + * But by using the combi matches this should not be necessary anymore! + * + * @param aGraphPattern The graph pattern of the binding set that is created. + * @param someCombiMatches The CombiMatches of the node. + * @param someNeighborBS The binding sets from all neighbors of a node. + * @return A TripleVarBindingSet that consists of only valid (according to the + * combimatches) bindings. + */ + public static TripleVarBindingSet translateWithCombiMatches(Set aGraphPattern, + Set someCombiMatches, Map someNeighborBS) { + var combinedTVBS = new TripleVarBindingSet(aGraphPattern); + + for (CombiMatch cMatch : someCombiMatches) { + // keep separate binding set per combi match + var cMatchTVBS = new TripleVarBindingSet(aGraphPattern); + for (Entry neighborEntry : someNeighborBS.entrySet()) { + + BaseRule neighborRule = neighborEntry.getKey(); + TripleVarBindingSet neighborBS = neighborEntry.getValue(); + + Set neighborMatches = cMatch.get(neighborRule); + + // take into account that very rarely multiple matches occur (i.e. in case of + // transitivity) + var neighborTVBS = new TripleVarBindingSet(aGraphPattern); + for (Match match : neighborMatches) { + // translate neighbor BS using match + // TODO change translate() method (if possible) to only take a single Match + // object. + var translated = neighborBS.translate(aGraphPattern, Set.of(match)); + neighborTVBS = neighborTVBS.merge2(translated); + } + + // merge with combined BS + cMatchTVBS.merge2(neighborTVBS); + } + + // addAll instead of merge, because different combi matches do not need to be + // combined. + combinedTVBS.addAll(cMatchTVBS.getBindings()); + } + + return combinedTVBS; + + } + public boolean isDone() { return this.done; } diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java index 391dcad5d..8976119d2 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java @@ -18,7 +18,7 @@ public class TripleVarBindingSet { private static final int PROGRESS_MILESTONE_SIZE = 1000; - private static final int LARGE_BS_SIZE = 30000; + private static final int LARGE_BS_SIZE = 300000; private Set graphPattern; private Set bindings; private Set tripleVarsCache; @@ -156,7 +156,9 @@ public String toString() { * Simply the union between the two bindingsets. Does nothing complex for now. * * @param aGraphBindingSet - * @return + * @return a NEW (!) binding set with the merged binding sets. + * @apiNote Note that this method does not modify the original (this) binding + * set. */ public TripleVarBindingSet merge(TripleVarBindingSet aGraphBindingSet) { @@ -206,6 +208,59 @@ public TripleVarBindingSet merge(TripleVarBindingSet aGraphBindingSet) { return gbs; } + /** + * Special merge that only combines the current bindings with the given + * bindings. It does not add the bindings separately. + * + * @param aGraphBindingSet + * @return a NEW (!) binding set with the merged binding sets. + * @apiNote Note that this method does not modify the original (this) binding + * set. + */ + public TripleVarBindingSet merge2(TripleVarBindingSet aGraphBindingSet) { + + LOG.trace("Merging {} bindings with our {} bindings.", aGraphBindingSet.getBindings().size(), + this.getBindings().size()); + + TripleVarBindingSet gbs = new TripleVarBindingSet(this.graphPattern); + + final int otherBindingSetSize = aGraphBindingSet.getBindings().size(); + final long totalCount = (long) otherBindingSetSize * (long) this.getBindings().size(); + if (totalCount > LARGE_BS_SIZE) + LOG.warn("Merging 2 large BindingSets ({} * {} = {}). This can take some time.", + aGraphBindingSet.getBindings().size(), this.getBindings().size(), totalCount); + + if (this.bindings.isEmpty()) { + gbs.addAll(aGraphBindingSet.getBindings()); + } else { + // Cartesian product is the base case + AtomicLong progress = new AtomicLong(0); + + final int milestoneSize = PROGRESS_MILESTONE_SIZE; + AtomicLong nextMilestone = new AtomicLong(milestoneSize); + + this.bindings.stream().parallel().forEach(tvb1 -> { + for (TripleVarBinding otherB : aGraphBindingSet.getBindings()) { + // always add a merged version of the two bindings, except when they conflict. + if (!tvb1.isConflicting(otherB)) { + gbs.add(tvb1.merge(otherB)); + } + } + final long current = progress.incrementAndGet(); + + if (totalCount > LARGE_BS_SIZE && current == nextMilestone.get()) { + LOG.trace("{}/{} BindingSet merge tasks done!", current * otherBindingSetSize, totalCount); + nextMilestone.set(current + milestoneSize); + } + }); + } + + if (totalCount > LARGE_BS_SIZE) + LOG.debug("Merging large BindingSets done!"); + + return gbs; + } + public boolean isEmpty() { return this.bindings.isEmpty(); From 88f209483986ee977b0ce8f9b9c1132a68e8522c Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 24 Jun 2025 09:37:45 +0200 Subject: [PATCH 3/9] TestAskAnswerLargeBindingSets works much faster now! There are still a lot of errors in reasoner tests, though. --- .../eu/knowledge/engine/reasoner/AntSide.java | 7 +- .../knowledge/engine/reasoner/BaseRule.java | 19 +- .../knowledge/engine/reasoner/ConsSide.java | 9 +- .../engine/reasoner/ReasonerPlan.java | 223 ++++++++---------- .../reasoner/api/TripleVarBindingSet.java | 37 ++- .../engine/reasoner/rulenode/AntRuleNode.java | 26 +- .../reasoner/rulenode/BindingSetStore.java | 146 +++++++++--- .../reasoner/rulenode/ConsRuleNode.java | 21 +- .../reasoner/rulenode/FullRuleNode.java | 41 +++- .../engine/reasoner/rulestore/MatchNode.java | 65 ++++- .../engine/reasoner/rulestore/RuleStore.java | 80 +++++-- .../reasoner/VerySimpleBackwardTest.java | 16 +- .../engine/reasoner/api/MatchTest.java | 16 +- .../reasoningnode/TestBindingSetStore.java | 143 ++++++----- .../api/TestGraphPatternMatch.java | 24 +- 15 files changed, 573 insertions(+), 300 deletions(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/AntSide.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/AntSide.java index 9ece3ed60..4d59b694c 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/AntSide.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/AntSide.java @@ -3,6 +3,7 @@ import java.util.Map; import java.util.Set; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; import eu.knowledge.engine.reasoner.rulenode.RuleNode; @@ -12,10 +13,14 @@ */ public interface AntSide { + public void setAntecedentCombiMatches(Set someCombiMatches); + + public Set getAntecedentCombiMatches(); + public void addAntecedentNeighbour(RuleNode neighbour, Set matches); public Map> getAntecedentNeighbours(); - public boolean addResultBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet bs); + public boolean addResultBindingSetInput(RuleNode aNeighbor, Map someBindingSets); } 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 a2ebccd4c..edaea4230 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/BaseRule.java @@ -183,7 +183,9 @@ public CompletableFuture handle(BindingSet aBindingSet) { return null; } }); - Map> matches = getMatches(this, new HashSet<>(Arrays.asList(r)), false, aMatchConfig); + Set combiMatches = getMatches(this, new HashSet<>(Arrays.asList(r)), false, aMatchConfig); + + Map> matches = convertToMap(combiMatches); if (matches.containsKey(r)) return matches.get(r); @@ -194,7 +196,8 @@ public CompletableFuture handle(BindingSet aBindingSet) { public Set antecedentMatches(Set aConsequent, EnumSet aMatchConfig) { if (!this.antecedent.isEmpty()) { Rule r = new Rule(new HashSet<>(), aConsequent); - Map> matches = getMatches(this, new HashSet<>(Arrays.asList(r)), true, aMatchConfig); + Set combiMatches = getMatches(this, new HashSet<>(Arrays.asList(r)), true, aMatchConfig); + Map> matches = convertToMap(combiMatches); if (matches.containsKey(r)) return matches.get(r); } @@ -388,7 +391,7 @@ public static Map> getMatchesPerTriplePerRule(Set * the target rule * @return A set of matches that all contribute to some full matche. */ - public static Map> getMatches(BaseRule aTargetRule, Set someCandidateRules, + public static Set getMatches(BaseRule aTargetRule, Set someCandidateRules, boolean antecedentOfTarget, EnumSet config) { Set targetGP = antecedentOfTarget ? aTargetRule.getAntecedent() : aTargetRule.getConsequent(); @@ -409,7 +412,7 @@ public static Map> getMatches(BaseRule aTargetRule, Set(); + return new HashSet<>(); printCombiMatchesPerTriple(aTargetRule, combiMatchesPerTriple); @@ -526,7 +529,7 @@ public static Map> getMatches(BaseRule aTargetRule, Set(allMatches); } private static void printAllMatches(List allMatches) { @@ -644,7 +647,7 @@ private static boolean doesSameCandidateTripleMapToDifferentTriple(Match candida return false; } - private static Map> convertToMap(List combiMatches) { + private static Map> convertToMap(Set combiMatches) { Map> matchesPerRule = new HashMap<>(); for (CombiMatch combiMatch : combiMatches) { for (Map.Entry> ruleMatch : combiMatch.entrySet()) { @@ -677,6 +680,10 @@ public CombiMatch() { super(); } + public CombiMatch(Map> someRules) { + super(someRules); + } + /** * @param aMatch a combi match object to check whether it is a sub combi match * of this combi match. diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ConsSide.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ConsSide.java index 4b64b4d20..9159553ad 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ConsSide.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ConsSide.java @@ -3,6 +3,7 @@ import java.util.Map; import java.util.Set; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; import eu.knowledge.engine.reasoner.rulenode.RuleNode; @@ -12,9 +13,13 @@ */ public interface ConsSide { + public void setConsequentCombiMatches(Set someMatches); + + public Set getConsequentCombiMatches(); + public void addConsequentNeighbour(RuleNode neighbour, Set matches); public Map> getConsequentNeighbours(); - - public boolean addFilterBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet bs); + + public boolean addFilterBindingSetInput(RuleNode aNeighbor, Map someBindingSets); } 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 a3da37bb3..fa6f7e99f 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/ReasonerPlan.java @@ -59,6 +59,97 @@ public ReasonerPlan(RuleStore aStore, ProactiveRule aStartRule, EnumSet { + 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); + } + }); + + // store the combi matches when there are any antecedents. + if (!currentRuleNode.getRule().getAntecedent().isEmpty()) + ((AntSide) currentRuleNode) + .setAntecedentCombiMatches(this.store.getAntecedentCombiMatches(currentRuleNode.getRule())); + } else { + // interested in both consequent and antecedent neighbors + + this.store.getConsequentNeighbors(aRule, this.matchConfig).forEach((rule, matches) -> { + if (!(rule instanceof ProactiveRule)) { + assert currentRuleNode instanceof ConsSide; + var newNode = createOrGetReasonerNode(rule, aRule); + ((ConsSide) currentRuleNode).addConsequentNeighbour(newNode, matches); + + var inverseMatches = Match.invertAll(matches); + ((AntSide) newNode).addAntecedentNeighbour(currentRuleNode, inverseMatches); + } else { + LOG.trace("Skipped proactive rule: {}", rule); + } + }); + + // store the combi matches when there are any antecedents. + if (!currentRuleNode.getRule().getConsequent().isEmpty()) + ((ConsSide) currentRuleNode) + .setConsequentCombiMatches(this.store.getConsequentCombiMatches(currentRuleNode.getRule())); + + // antecedent neighbors to propagate bindings further via backward chaining + + // determine whether our parent matches us partially + boolean ourAntecedentFullyMatchesParentConsequent = false; + + Map> antecedentNeighbors = this.store.getAntecedentNeighbors(aRule, this.matchConfig); + if (aParent != null && antecedentNeighbors.containsKey(aParent)) { + ourAntecedentFullyMatchesParentConsequent = antecedentFullyMatchesConsequent(aRule, aParent, + antecedentNeighbors.get(aParent)); + } + + if (!ourAntecedentFullyMatchesParentConsequent) { + + 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); + } + }); + + if (!currentRuleNode.getRule().getAntecedent().isEmpty()) + ((AntSide) currentRuleNode) + .setAntecedentCombiMatches(this.store.getAntecedentCombiMatches(currentRuleNode.getRule())); + } + } + + return currentRuleNode; + } + /** * 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 @@ -134,6 +225,7 @@ public TaskBoard execute(BindingSet bindingSet) { ((AntSide) current).getAntecedentNeighbours().forEach((n, matches) -> { var translated = toBeFilterPropagated.translate(n.getRule().getConsequent(), Match.invertAll(matches)); + boolean itChanged = ((ConsSide) n).addFilterBindingSetInput(current, translated); if (itChanged) { changed.add(n); @@ -173,59 +265,6 @@ public TaskBoard execute(BindingSet bindingSet) { return taskBoard; } - /** - * Translates the given TripleVarBindingSets from neighbors to a single - * TripleVarBindingSet using the given combi matches. Because the combi matches - * are very specific to how they should be formed, using this information should - * speed up the binding set merging process considerably. In the previous (old) - * version, the binding sets were all being merge in every possible way to make - * sure all possible bindings were present (also for cases like transitivity). - * But by using the combi matches this should not be necessary anymore! - * - * @param aGraphPattern The graph pattern of the binding set that is created. - * @param someCombiMatches The CombiMatches of the node. - * @param someNeighborBS The binding sets from all neighbors of a node. - * @return A TripleVarBindingSet that consists of only valid (according to the - * combimatches) bindings. - */ - public static TripleVarBindingSet translateWithCombiMatches(Set aGraphPattern, - Set someCombiMatches, Map someNeighborBS) { - var combinedTVBS = new TripleVarBindingSet(aGraphPattern); - - for (CombiMatch cMatch : someCombiMatches) { - // keep separate binding set per combi match - var cMatchTVBS = new TripleVarBindingSet(aGraphPattern); - for (Entry neighborEntry : someNeighborBS.entrySet()) { - - BaseRule neighborRule = neighborEntry.getKey(); - TripleVarBindingSet neighborBS = neighborEntry.getValue(); - - Set neighborMatches = cMatch.get(neighborRule); - - // take into account that very rarely multiple matches occur (i.e. in case of - // transitivity) - var neighborTVBS = new TripleVarBindingSet(aGraphPattern); - for (Match match : neighborMatches) { - // translate neighbor BS using match - // TODO change translate() method (if possible) to only take a single Match - // object. - var translated = neighborBS.translate(aGraphPattern, Set.of(match)); - neighborTVBS = neighborTVBS.merge2(translated); - } - - // merge with combined BS - cMatchTVBS.merge2(neighborTVBS); - } - - // addAll instead of merge, because different combi matches do not need to be - // combined. - combinedTVBS.addAll(cMatchTVBS.getBindings()); - } - - return combinedTVBS; - - } - public boolean isDone() { return this.done; } @@ -271,84 +310,6 @@ public boolean isBackward() { return !this.start.getAntecedent().isEmpty() && this.start.getConsequent().isEmpty(); } - private RuleNode createOrGetReasonerNode(BaseRule aRule, BaseRule aParent) { - - final RuleNode reasonerNode; - if (this.ruleToRuleNode.containsKey(aRule)) - return this.ruleToRuleNode.get(aRule); - else { - reasonerNode = this.newNode(aRule); - } - - // build the reasoner node graph - this.ruleToRuleNode.put(aRule, reasonerNode); - - if (isBackward()) { - // for now we only are interested in antecedent neighbors. - // TODO for looping we DO want to consider consequent neighbors as well. - - this.store.getAntecedentNeighbors(aRule, this.matchConfig).forEach((rule, matches) -> { - if (!(rule instanceof ProactiveRule)) { - assert reasonerNode instanceof AntSide; - var newNode = createOrGetReasonerNode(rule, aRule); - assert newNode instanceof ConsSide; - ((AntSide) reasonerNode).addAntecedentNeighbour(newNode, matches); - - var inverseMatches = Match.invertAll(matches); - // TODO: Validate with Barry if we can use the same `matches` object here - ((ConsSide) newNode).addConsequentNeighbour(reasonerNode, inverseMatches); - } else { - LOG.trace("Skipped proactive rule: {}", rule); - } - }); - } else { - // interested in both consequent and antecedent neighbors - this.store.getConsequentNeighbors(aRule, this.matchConfig).forEach((rule, matches) -> { - if (!(rule instanceof ProactiveRule)) { - assert reasonerNode instanceof ConsSide; - var newNode = createOrGetReasonerNode(rule, aRule); - ((ConsSide) reasonerNode).addConsequentNeighbour(newNode, matches); - - var inverseMatches = Match.invertAll(matches); - // TODO: Validate with Barry if we can use the same `matches` object here - ((AntSide) newNode).addAntecedentNeighbour(reasonerNode, inverseMatches); - } else { - LOG.trace("Skipped proactive rule: {}", rule); - } - }); - - // antecedent neighbors to propagate bindings further via backward chaining - - // determine whether our parent matches us partially - boolean ourAntecedentFullyMatchesParentConsequent = false; - - Map> antecedentNeighbors = this.store.getAntecedentNeighbors(aRule, this.matchConfig); - if (aParent != null && antecedentNeighbors.containsKey(aParent)) { - ourAntecedentFullyMatchesParentConsequent = antecedentFullyMatchesConsequent(aRule, aParent, - antecedentNeighbors.get(aParent)); - } - - if (!ourAntecedentFullyMatchesParentConsequent) { - antecedentNeighbors.forEach((rule, matches) -> { - if (!(rule instanceof ProactiveRule)) { - assert reasonerNode instanceof AntSide; - var newNode = createOrGetReasonerNode(rule, aRule); - assert newNode instanceof ConsSide; - ((AntSide) reasonerNode).addAntecedentNeighbour(newNode, matches); - - // TODO: Validate with Barry if we can use the same `matches` object here - var inverseMatches = Match.invertAll(matches); - ((ConsSide) newNode).addConsequentNeighbour(reasonerNode, inverseMatches); - } else { - LOG.trace("Skipped proactive rule: {}", rule); - } - }); - } - } - - return reasonerNode; - } - private void scheduleOrDoTask(RuleNode current, TaskBoard taskBoard) { if (this.useTaskBoard) { taskBoard.addTask(current); diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java index 8976119d2..66b228c8f 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java @@ -1,6 +1,7 @@ package eu.knowledge.engine.reasoner.api; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -268,7 +269,7 @@ public boolean isEmpty() { } /** - * Translate this bindingset using the given match. The variablenames will be + * Translate this bindingset using the given matches. The variable names will be * changed and variables not relevant in the match will be removed. * * The format of the mapping is expected to be translate , @@ -276,21 +277,31 @@ public boolean isEmpty() { * * It also filters bindings that are incompatible with the match. * - * @param match - * @return + * The resulting TripleVarBindingSets are stored per match to allow follow-up + * computation to be more efficient. + * + * @param someMatches The matches to use for this translation. + * @return A mapping from each match to a triplevarbindingset. */ - public TripleVarBindingSet translate(Set graphPattern, Set match) { - LOG.trace("Translating binding set with '{}' bindings and '{}' matches.", this.bindings.size(), match.size()); + public Map translate(Set aGraphPattern, Set someMatches) { + LOG.trace("Translating binding set with '{}' bindings and '{}' matches.", this.bindings.size(), + someMatches.size()); long start = System.currentTimeMillis(); - - TripleVarBindingSet newOne = new TripleVarBindingSet(graphPattern); + Map bsPerMatch = new HashMap<>(); TripleVarBinding toB; for (TripleVarBinding fromB : this.bindings) { - for (Match entry : match) { + for (Match aMatch : someMatches) { + + TripleVarBindingSet matchBS = bsPerMatch.get(aMatch); + if (matchBS == null) { + matchBS = new TripleVarBindingSet(aGraphPattern); + bsPerMatch.put(aMatch, matchBS); + } + boolean skip = false; toB = new TripleVarBinding(); - for (Map.Entry keyValue : entry.getMatchingPatterns().entrySet()) { + for (Map.Entry keyValue : aMatch.getMatchingPatterns().entrySet()) { TriplePattern fromTriple = keyValue.getKey(); TriplePattern toTriple = keyValue.getValue(); Map mapping = fromTriple.findMatches(toTriple); @@ -329,13 +340,13 @@ public TripleVarBindingSet translate(Set graphPattern, Set } } if (!skip) - newOne.add(toB); + matchBS.add(toB); } } LOG.trace("Translated binding set with '{}' bindings and '{}' matches in '{}ms'.", this.bindings.size(), - match.size(), System.currentTimeMillis() - start); - return newOne; + someMatches.size(), System.currentTimeMillis() - start); + return bsPerMatch; } @@ -348,7 +359,7 @@ public void addAll(Set permutatedTVBs) { * the bindings in the given {@code bindingSet}. * * @param bindingSet - * @return + * @return A new binding set that only contains the compatible bindings. */ public TripleVarBindingSet keepCompatible(TripleVarBindingSet bindingSet) { diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/AntRuleNode.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/AntRuleNode.java index 9575fbf9c..9caf4a89e 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/AntRuleNode.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/AntRuleNode.java @@ -4,9 +4,11 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import eu.knowledge.engine.reasoner.AntSide; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; import eu.knowledge.engine.reasoner.rulestore.RuleStore; @@ -34,6 +36,21 @@ public AntRuleNode(BaseRule aRule) { */ private Map> antecedentNeighbours = new HashMap<>(); + private Set antecedentCombiMatches; + + @Override + public void setAntecedentCombiMatches(Set someCombiMatches) { + this.antecedentCombiMatches = someCombiMatches; + + // also set the combi matches to the rule store. + this.resultBindingSetInput.setCombiMatches(this.antecedentCombiMatches); + } + + @Override + public Set getAntecedentCombiMatches() { + return this.antecedentCombiMatches; + } + @Override public void addAntecedentNeighbour(RuleNode neighbour, Set matches) { this.antecedentNeighbours.put(neighbour, matches); @@ -50,11 +67,14 @@ public Set getAllNeighbours() { } @Override - public boolean addResultBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet aBindingSet) { + public boolean addResultBindingSetInput(RuleNode aNeighbor, Map someBindingSets) { assert (antecedentNeighbours.keySet().contains(aNeighbor)); - TripleVarBindingSet filteredBS = aBindingSet; + Map filteredBS = someBindingSets; if (this.filterBindingSetOutput != null && !this.hasProactiveParent(aNeighbor)) { - filteredBS = aBindingSet.keepCompatible(this.filterBindingSetOutput); + + for (Map.Entry matchBS : filteredBS.entrySet()) { + filteredBS.put(matchBS.getKey(), matchBS.getValue().keepCompatible(this.filterBindingSetOutput)); + } } var changed = this.resultBindingSetInput.add(aNeighbor, filteredBS); 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 9a0cabbde..50b89d884 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 @@ -5,11 +5,15 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; import org.apache.jena.graph.Node; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; +import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.api.TripleNode; import eu.knowledge.engine.reasoner.api.TriplePattern; import eu.knowledge.engine.reasoner.api.TripleVarBinding; @@ -26,7 +30,13 @@ public class BindingSetStore { private final Set neighbors; - private final Map neighborBindingSet = new HashMap<>(); + private final Map> neighborBindingSet = new HashMap<>(); + + /** + * This combi matches set need to be initialized after the + * constructor has been called. + */ + private Set combiMatches = null; private final Set graphPattern; /** @@ -43,19 +53,20 @@ public BindingSetStore(Set aGraphPattern, Set someNeigh * Register the given {@code aBindingSet} to this BindingSetStore as coming from * neighbor {@code aNeighbor} * - * @param aNeighbor the neighbor who brought this bindingset. - * @param aBindingSet the bindingset brought by this neighbor. + * @param aNeighbor the neighbor who brought this bindingset. + * @param someBindingSets the bindingset brought by this neighbor. * @return whether adding this bindingset changed anything. I.e. this particular * neighbor did not yet give a bindingset or this bindingset is * different from the previous one. */ - public boolean add(RuleNode aNeighbor, TripleVarBindingSet aBindingSet) { - assert aBindingSet != null; + public boolean add(RuleNode aNeighbor, Map someBindingSets) { + assert someBindingSets != null; assert neighbors.contains(aNeighbor); - TripleVarBindingSet previousBindingSet = this.neighborBindingSet.put(aNeighbor, aBindingSet); + Map previousBindingSet = this.neighborBindingSet.put(aNeighbor.getRule(), + someBindingSets); - boolean changed = previousBindingSet == null || !previousBindingSet.equals(aBindingSet); + boolean changed = previousBindingSet == null || !previousBindingSet.equals(someBindingSets); if (changed) this.cache = null; @@ -64,17 +75,78 @@ public boolean add(RuleNode aNeighbor, TripleVarBindingSet aBindingSet) { } public boolean haveAllNeighborsContributed() { - return this.neighborBindingSet.keySet().containsAll(neighbors); + return this.neighborBindingSet.keySet() + .containsAll(neighbors.stream().map(x -> x.getRule()).collect(Collectors.toSet())); } // TODO: It feels ugly to do this. public boolean haveAllNeighborsContributedExcept(Set nodes) { - var allNeighboursWithException = new HashSet<>(); - allNeighboursWithException.addAll(neighbors); - allNeighboursWithException.removeAll(nodes); + var allNeighboursWithException = new HashSet(); + allNeighboursWithException.addAll(neighbors.stream().map(x -> x.getRule()).collect(Collectors.toSet())); + allNeighboursWithException.removeAll(nodes.stream().map(x -> x.getRule()).collect(Collectors.toSet())); return this.neighborBindingSet.keySet().containsAll(allNeighboursWithException); } + /** + * Translates the given TripleVarBindingSets from neighbors to a single + * TripleVarBindingSet using the given combi matches. Because the combi matches + * are very specific to how they should be formed, using this information should + * speed up the binding set merging process considerably. In the previous (old) + * version, the binding sets were all being merge in every possible way to make + * sure all possible bindings were present (also for cases like transitivity). + * But by using the combi matches this should not be necessary anymore! + * + * @param aGraphPattern The graph pattern of the binding set that is created. + * @param someCombiMatches The CombiMatches of the node. + * @param someNeighborBS The already translated binding sets from all + * neighbors of a node per match. + * @return A TripleVarBindingSet that consists of only valid (according to the + * combimatches) bindings. + */ + private TripleVarBindingSet translateWithCombiMatches(Set aGraphPattern, + Set someCombiMatches, Map> someNeighborBS) { + var combinedTVBS = new TripleVarBindingSet(aGraphPattern); + + for (CombiMatch cMatch : someCombiMatches) { + // keep separate binding set per combi match + var cMatchTVBS = new TripleVarBindingSet(aGraphPattern); + + for (Entry> cEntry : cMatch.entrySet()) { + + BaseRule aNeighborRule = cEntry.getKey(); + + Map matchToBS = someNeighborBS.get(aNeighborRule); + + if (matchToBS != null) { + for (Match cSingleMatch : cEntry.getValue()) { + TripleVarBindingSet tvbs = matchToBS.get(cSingleMatch); + + if (tvbs != null) + cMatchTVBS.addAll(cMatchTVBS.merge2(tvbs).getBindings()); + } + } + } + + // addAll instead of merge, because different combi matches do not need to be + // combined. + combinedTVBS.addAll(cMatchTVBS.getBindings()); + } + + return combinedTVBS; + + } + + /** + * Sets the combi matches for this binding set store. Combi matches contain a + * combination of matches with neighboring rules that together form a single + * match for the rule node of which this bindingset store is parts. + * + * @param someCombiMatches The combi matches for this store. + */ + public void setCombiMatches(Set someCombiMatches) { + this.combiMatches = someCombiMatches; + } + /** * @return the bindingset with the combined bindingset of all neighbors. */ @@ -83,20 +155,26 @@ public TripleVarBindingSet get() { if (this.cache != null) { return this.cache; } - // TODO: Can a similar assertion be made? (Changed the class so that the - // result is also gettable when all neighbours except a specific one - // contributed) - // assert haveAllNeighborsContributed(); - - TripleVarBindingSet combinedBS = new TripleVarBindingSet(graphPattern); - for (TripleVarBindingSet bs : this.neighborBindingSet.values()) { - combinedBS = combinedBS.merge(bs); + + // unfortunately, due to the asymmetry introduced with combi matches (i.e. combi + // matches are available at the antecedent neighbours, but not at the consequent + // neighbors) we use the older (slower) method when there are no combi matches. + if (this.combiMatches != null) + this.cache = this.translateWithCombiMatches(this.graphPattern, this.combiMatches, this.neighborBindingSet); + else { + TripleVarBindingSet combinedBS = new TripleVarBindingSet(graphPattern); + + for (TripleVarBindingSet bs : this.neighborBindingSet.values().stream().map(x -> x.values()) + .flatMap(x -> x.stream()).collect(Collectors.toSet())) { + combinedBS = combinedBS.merge(bs); + } + + // NOTE: we merge the bindings with themselves here (when the bindings + // 'leave' the store), but it may be better to do it when they enter the + // store, or when they get translated/matched. + this.cache = combinedBS.merge(combinedBS); } - // NOTE: we merge the bindings with themselves here (when the bindings - // 'leave' the store), but it may be better to do it when they enter the - // store, or when they get translated/matched. - this.cache = combinedBS.merge(combinedBS); return this.cache; } @@ -116,21 +194,22 @@ private String getName(BaseRule r) { public void printDebuggingTable() { StringBuilder table = new StringBuilder(); - List allNeighbors = new ArrayList<>(neighbors); + List allNeighbors = new ArrayList<>( + neighbors.stream().map(x -> x.getRule()).collect(Collectors.toSet())); // header row int count = 0; table.append("| Graph Pattern |"); count++; - for (RuleNode neighbor : allNeighbors) { + for (BaseRule neighbor : allNeighbors) { if (this.neighborBindingSet.containsKey(neighbor)) { - for (int i = 0; i < this.neighborBindingSet.get(neighbor).getBindings().size(); i++) { - table.append(this.getName(neighbor.getRule()) + "-" + i).append(" | "); + for (int i = 0; i < combine(this.neighborBindingSet.get(neighbor)).getBindings().size(); i++) { + table.append(this.getName(neighbor) + "-" + i).append(" | "); count++; } } else { - table.append(this.getName(neighbor.getRule())).append(" | "); + table.append(this.getName(neighbor)).append(" | "); count++; } } @@ -150,9 +229,9 @@ public void printDebuggingTable() { table.append(" | ").append(tp.toString()).append(" | "); // bindings - for (RuleNode neigh : allNeighbors) { + for (BaseRule neigh : allNeighbors) { - TripleVarBindingSet tvbs = this.neighborBindingSet.get(neigh); + TripleVarBindingSet tvbs = combine(this.neighborBindingSet.get(neigh)); if (tvbs != null) { for (TripleVarBinding tvb : tvbs.getBindings()) { @@ -201,4 +280,11 @@ private String formatNode(Node n) { return n.isVariable() ? before + TriplePattern.trunc(n) + after : TriplePattern.trunc(n); } + private TripleVarBindingSet combine(Map someBindingSets) { + var resultBS = new TripleVarBindingSet(this.graphPattern); + for (TripleVarBindingSet bs : someBindingSets.values()) { + resultBS.addAll(bs.getBindings()); + } + return resultBS; + } } diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/ConsRuleNode.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/ConsRuleNode.java index e8303a0c5..329902e1c 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/ConsRuleNode.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/rulenode/ConsRuleNode.java @@ -4,8 +4,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.ConsSide; import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; @@ -31,6 +33,21 @@ public ConsRuleNode(BaseRule aRule) { */ private Map> consequentNeighbours = new HashMap<>(); + private Set consequentCombiMatches; + + @Override + public void setConsequentCombiMatches(Set someMatches) { + this.consequentCombiMatches = someMatches; + + // also set the combi matches to the binding set store. + this.filterBindingSetInput.setCombiMatches(someMatches); + } + + @Override + public Set getConsequentCombiMatches() { + return this.consequentCombiMatches; + } + @Override public void addConsequentNeighbour(RuleNode neighbour, Set matches) { this.consequentNeighbours.put(neighbour, matches); @@ -47,9 +64,9 @@ public Set getAllNeighbours() { } @Override - public boolean addFilterBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet bs) { + public boolean addFilterBindingSetInput(RuleNode aNeighbor, Map someBindingSets) { assert this.consequentNeighbours.containsKey(aNeighbor); - return this.filterBindingSetInput.add(aNeighbor, bs); + return this.filterBindingSetInput.add(aNeighbor, someBindingSets); } @Override 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 f1c2a6840..a19ac6501 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 @@ -11,12 +11,12 @@ import java.util.concurrent.Future; import java.util.stream.Collectors; -import org.apache.jena.atlas.logging.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.knowledge.engine.reasoner.AntSide; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.ConsSide; import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.Rule; @@ -44,6 +44,31 @@ public FullRuleNode(BaseRule aRule) { private Map> antecedentNeighbours = new HashMap<>(); private Map> consequentNeighbours = new HashMap<>(); + private Set antecedentCombiMatches; + private Set consequentCombiMatches; + + public void setAntecedentCombiMatches(Set someMatches) { + this.antecedentCombiMatches = someMatches; + + // also set the combi matches to the binding set store + this.resultBindingSetInput.setCombiMatches(someMatches); + } + + public Set getAntecedentCombiMatches() { + return this.antecedentCombiMatches; + } + + public void setConsequentCombiMatches(Set someMatches) { + this.consequentCombiMatches = someMatches; + + // also set the combi matches to the binding set store + this.filterBindingSetInput.setCombiMatches(someMatches); + } + + public Set getConsequentCombiMatches() { + return this.consequentCombiMatches; + } + @Override public void addConsequentNeighbour(RuleNode neighbour, Set matches) { this.consequentNeighbours.put(neighbour, matches); @@ -75,16 +100,20 @@ public Set getAllNeighbours() { } @Override - public boolean addFilterBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet bs) { - return this.filterBindingSetInput.add(aNeighbor, bs); + public boolean addFilterBindingSetInput(RuleNode aNeighbor, Map someBindingSets) { + assert this.consequentNeighbours.containsKey(aNeighbor); + return this.filterBindingSetInput.add(aNeighbor, someBindingSets); } @Override - public boolean addResultBindingSetInput(RuleNode aNeighbor, TripleVarBindingSet bs) { + public boolean addResultBindingSetInput(RuleNode aNeighbor, Map someBindingSets) { - TripleVarBindingSet filteredBS = bs; + Map filteredBS = someBindingSets; if (this.filterBindingSetOutput != null && !this.hasProactiveParent(aNeighbor)) { - filteredBS = bs.keepCompatible(this.filterBindingSetOutput); + + for (Map.Entry matchBS : filteredBS.entrySet()) { + filteredBS.put(matchBS.getKey(), matchBS.getValue().keepCompatible(this.filterBindingSetOutput)); + } } var changed = this.resultBindingSetInput.add(aNeighbor, filteredBS); 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 bcf8e317f..e1002f634 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 @@ -1,11 +1,12 @@ package eu.knowledge.engine.reasoner.rulestore; -import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.BaseRule.MatchFlag; import eu.knowledge.engine.reasoner.Match; @@ -30,21 +31,47 @@ public class MatchNode { /** * All other rules in the {@link MatchNode#store} whose consequents match this - * rule's antecedent according to the {@link MatchStrategy#NORMAL_LEVEL} + * rule's antecedent according to the configured {@link MatchFlag} */ private Map> antecedentNeighbors; + /** + * The combi matches to antecedent neighbors. + * + * These might not get initialized, because we cannot initialize these + * symmetrically like the antencedentNeighbors mapping can be initialized. + * + * If this set remains null, it is not applicable. If it is empty, it is + * delibarately initalized with an empty list, because there are no combi + * matches on the antecedent side of this node.If it is non-empty, there are + * combi matches to one or more nodes on the consequent side of this node. + */ + private Set antecedentCombiMatches; + /** * All other rules in the {@link MatchNode#store} whose antecedents match this - * rule's consequent according to the {@link MatchStrategy#NORMAL_LEVEL} + * rule's consequent according to the {@link MatchFlag} */ private Map> consequentNeighbors; + /** + * The combi matches to consequent neighbors. + * + * Thesemightnot get initialize,because we cannot initialize these symmetrically + * like the consequentNeighbors mapping can be initialized. + * + * If this set remains null, it is not applicable. If it is empty, it is + * delibarately initalized with an empty list, because there are no combi + * matches on the consequent side of this node. If it is non-empty, there are + * combi matches to one or more nodes on the consequent side of this node. + * + */ + private Set consequentCombiMatches; + public MatchNode(BaseRule aRule) { this.rule = aRule; this.antecedentNeighbors = new HashMap<>(); this.consequentNeighbors = new HashMap<>(); - } public BaseRule getRule() { @@ -67,6 +94,34 @@ public void setAntecedentNeighbor(BaseRule aRule, Set someMatches) { } } + /** + * Set the combi matches for the consequent side of this node. + * + * @param someCombiMatches The combi matches for this node on the consequent + * side. + */ + public void setConsequentMatches(Set someCombiMatches) { + this.consequentCombiMatches = someCombiMatches; + } + + /** + * Set the combi matches for the antecedent side of this node. + * + * @param someCombiMatches The combi matches for this node on the antecedent + * side. + */ + public void setAntecedentCombiMatches(Set someCombiMatches) { + this.antecedentCombiMatches = someCombiMatches; + } + + public Set getConsequentCombiMatches() { + return this.consequentCombiMatches; + } + + public Set getAntecedentCombiMatches() { + return this.antecedentCombiMatches; + } + public Map> getConsequentNeighbors() { return this.consequentNeighbors; } @@ -83,7 +138,9 @@ public RuleStore getStore() { } public void reset() { + this.antecedentCombiMatches.clear(); this.antecedentNeighbors.clear(); + this.consequentCombiMatches.clear(); this.consequentNeighbors.clear(); } 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 123116de0..ca02f18f4 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 @@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory; import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.reasoner.BaseRule.MatchFlag; import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.ProactiveRule; @@ -41,18 +42,18 @@ public class RuleStore { /** * All the rules in this store. */ - private Map ruleToRuleNode; + private Map ruleToMatchNode; /** * Instantiate an empty rule store. */ public RuleStore() { - ruleToRuleNode = new HashMap<>(); + ruleToMatchNode = new HashMap<>(); } public void addRule(BaseRule aRule) { - MatchNode aRuleNode = new MatchNode(aRule); - this.ruleToRuleNode.put(aRule, aRuleNode); + MatchNode aMatchNode = new MatchNode(aRule); + this.ruleToMatchNode.put(aRule, aMatchNode); } /** @@ -70,7 +71,7 @@ public void addRules(Set someRules) { */ public Set getRules() { - return this.ruleToRuleNode.keySet(); + return this.ruleToMatchNode.keySet(); } /** @@ -89,28 +90,55 @@ public Map> getAntecedentNeighbors(BaseRule aRule) { * this rule's antecedent. */ public Map> getAntecedentNeighbors(BaseRule aRule, EnumSet aConfig) { - MatchNode aRuleNode = this.ruleToRuleNode.get(aRule); + MatchNode aMatchNode = this.ruleToMatchNode.get(aRule); - assert aRuleNode != null; + assert aMatchNode != null; - Map> newMapping = BaseRule.getMatches(aRule, this.getRules(), true, aConfig); + // calculate matches + Set combiMatches = BaseRule.getMatches(aRule, this.getRules(), true, aConfig); + + // store combi matches + aMatchNode.setAntecedentCombiMatches(combiMatches); + + // store normal matches + Map> newMapping = convertToMapping(combiMatches); for (Map.Entry> entry : newMapping.entrySet()) { - aRuleNode.setAntecedentNeighbor(entry.getKey(), Match.invertAll(entry.getValue())); - this.ruleToRuleNode.get(entry.getKey()).setConsequentNeighbor(aRule, entry.getValue()); + aMatchNode.setAntecedentNeighbor(entry.getKey(), Match.invertAll(entry.getValue())); + this.ruleToMatchNode.get(entry.getKey()).setConsequentNeighbor(aRule, entry.getValue()); } return newMapping; } - 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<>()); + public Set getAntecedentCombiMatches(BaseRule aRule) { + assert aRule != null; + MatchNode mn = this.ruleToMatchNode.get(aRule); + return mn.getAntecedentCombiMatches(); + } + + public Set getConsequentCombiMatches(BaseRule aRule) { + assert aRule != null; + MatchNode mn = this.ruleToMatchNode.get(aRule); + return mn.getConsequentCombiMatches(); + } + + private Map> convertToMapping(Set someMatches) { + var mapping = new HashMap>(); + + for (CombiMatch cm : someMatches) { + for (Map.Entry> entry : cm.entrySet()) { + // get rule match set + var ruleMatchSet = mapping.get(entry.getKey()); + if (ruleMatchSet == null) { + ruleMatchSet = new HashSet(); + mapping.put(entry.getKey(), ruleMatchSet); + } + ruleMatchSet.addAll(entry.getValue()); + } } - Set matches = newMapping.get(entryToAdd.getKey()); - matches.add(entryToAdd.getValue()); + return mapping; } /** @@ -118,7 +146,6 @@ public void addToNewMapping(Map> newMapping, Map.Entry> getConsequentNeighbors(BaseRule aRule) { return this.getConsequentNeighbors(aRule, EnumSet.noneOf(MatchFlag.class)); - } /** @@ -132,15 +159,20 @@ public Map> getConsequentNeighbors(BaseRule aRule) { * this rule's consequent. */ public Map> getConsequentNeighbors(BaseRule aRule, EnumSet aConfig) { - MatchNode aRuleNode = this.ruleToRuleNode.get(aRule); + MatchNode aMatchNode = this.ruleToMatchNode.get(aRule); + + assert aMatchNode != null; - assert aRuleNode != null; + // calculate matches + Set combiMatches = BaseRule.getMatches(aRule, this.getRules(), false, aConfig); - Map> newMapping = BaseRule.getMatches(aRule, this.getRules(), false, aConfig); + // store combi matches + Map> newMapping = convertToMapping(combiMatches); + // store normal matches for (Map.Entry> entry : newMapping.entrySet()) { - aRuleNode.setConsequentNeighbor(entry.getKey(), Match.invertAll(entry.getValue())); - this.ruleToRuleNode.get(entry.getKey()).setAntecedentNeighbor(aRule, entry.getValue()); + aMatchNode.setConsequentNeighbor(entry.getKey(), Match.invertAll(entry.getValue())); + this.ruleToMatchNode.get(entry.getKey()).setAntecedentNeighbor(aRule, entry.getValue()); } return newMapping; @@ -151,7 +183,7 @@ public Map> getConsequentNeighbors(BaseRule aRule, EnumSet< * the neighbor of who. */ public void reset() { - for (MatchNode r : this.ruleToRuleNode.values()) { + for (MatchNode r : this.ruleToMatchNode.values()) { r.reset(); } } @@ -178,7 +210,7 @@ public String getGraphVizCode(ReasonerPlan aPlan, boolean urlOnly) { sb.append("strict digraph {\n"); Map ruleToName = new HashMap<>(); - for (MatchNode r : ruleToRuleNode.values()) { + for (MatchNode r : ruleToMatchNode.values()) { String currentName = ruleToName.get(r.getRule()); if (currentName == null) { diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/VerySimpleBackwardTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/VerySimpleBackwardTest.java index e78d69769..9f3e7e5a3 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/VerySimpleBackwardTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/VerySimpleBackwardTest.java @@ -3,6 +3,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -16,6 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.knowledge.engine.reasoner.BaseRule.MatchFlag; import eu.knowledge.engine.reasoner.ProactiveRule; import eu.knowledge.engine.reasoner.ReasonerPlan; import eu.knowledge.engine.reasoner.Rule; @@ -75,14 +77,13 @@ public void init() throws URISyntaxException { // Initialize store = new RuleStore(); bindingSetHandler = new ProxyDataBindingSetHandler(new Table(new String[] { - //@formatter:off + // @formatter:off "a", "b" - //@formatter:on + // @formatter:on }, new String[] { - //@formatter:off - ",22", - ",21", - //@formatter:on + // @formatter:off + ",22", ",21", + // @formatter:on })); store.addRule(new Rule( new HashSet<>( @@ -103,7 +104,8 @@ public void init() throws URISyntaxException { @Test public void doReasoning() throws InterruptedException, ExecutionException { // Start reasoning - ReasonerPlan plan = new ReasonerPlan(store, startRule); + ReasonerPlan plan = new ReasonerPlan(store, startRule, + EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); BindingSet bs = new BindingSet(); Binding binding2 = new Binding(); 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 fd5179847..fcc860c88 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 @@ -165,7 +165,8 @@ public void testGPMatcher7() { BaseRule r = new ProactiveRule(obj, new HashSet<>()); - Set findMatchesWithConsequent = r.antecedentMatches(obj, EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); + Set findMatchesWithConsequent = r.antecedentMatches(obj, + EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); System.out.println("Size: " + findMatchesWithConsequent.size()); for (Match m : findMatchesWithConsequent) { @@ -340,7 +341,8 @@ public void testGPMatcher12() { }); Set findMatchesWithAntecedent = r.antecedentMatches( - new HashSet<>(Arrays.asList(/* t1, */ t5, t9, t8, t7, t6, t4, t3)), EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); + new HashSet<>(Arrays.asList(/* t1, */ t5, t9, t8, t7, t6, t4, t3)), + EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); System.out.println("Size: " + findMatchesWithAntecedent.size()); // System.out.println(findMatchesWithConsequent); @@ -491,7 +493,7 @@ public void testBugTranslate() { } @Test - public void testOtherBurgTranslate() { + public void testOtherBugTranslate() { TriplePattern t1 = new TriplePattern("?p ?t"); TriplePattern t2 = new TriplePattern("?p ?q"); Set obj = new HashSet<>(Arrays.asList(t1, t2)); @@ -544,8 +546,9 @@ public void testTranslateEmptyBindingSet() { var match = new Match(t1, t2, map); - TripleVarBindingSet tvbs2 = tvbs1.translate(new HashSet<>(Arrays.asList(t2)), - new HashSet<>(Arrays.asList(match))); + TripleVarBindingSet tvbs2 = tvbs1 + .translate(new HashSet<>(Arrays.asList(t2)), new HashSet<>(Arrays.asList(match))).values().iterator() + .next(); System.out.println("BindingSet: " + tvbs2); @@ -594,7 +597,8 @@ public CompletableFuture handle(BindingSet aBindingSet) { System.out.println("NrOfMatches with " + obj.size() + " triple patterns: " + getNumberOfMatches(obj.size())); - Set findMatchesWithAntecedent = r.antecedentMatches(obj, EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); + Set findMatchesWithAntecedent = r.antecedentMatches(obj, + EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); System.out.println("Size: " + findMatchesWithAntecedent.size()); diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java index 2daa83684..dd1f87116 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java @@ -4,81 +4,98 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; +import eu.knowledge.engine.reasoner.BaseRule; +import eu.knowledge.engine.reasoner.Match; import eu.knowledge.engine.reasoner.Rule; import eu.knowledge.engine.reasoner.api.BindingSet; import eu.knowledge.engine.reasoner.api.TriplePattern; +import eu.knowledge.engine.reasoner.api.TripleVarBindingSet; import eu.knowledge.engine.reasoner.api.Util; import eu.knowledge.engine.reasoner.rulenode.ActiveConsRuleNode; import eu.knowledge.engine.reasoner.rulenode.BindingSetStore; import eu.knowledge.engine.reasoner.rulenode.RuleNode; public class TestBindingSetStore { - @Test - public void testAddingEmptyBindingSetTwice() { - Set graphPattern = new HashSet<>(); - var tp = new TriplePattern("?s :prop ?o"); - graphPattern.add(tp); - - Set ruleNodes = new HashSet<>(); - var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); - ruleNodes.add(neighbour); - - BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); - - assertFalse(bss.haveAllNeighborsContributed()); - var changed = bss.add(neighbour, new BindingSet().toTripleVarBindingSet(graphPattern)); - assertTrue(changed); - changed = bss.add(neighbour, new BindingSet().toTripleVarBindingSet(graphPattern)); - assertFalse(changed); - assertTrue(bss.haveAllNeighborsContributed()); - } - - @Test - public void testAddingNonEmptyBindingSetTwice() { - Set graphPattern = new HashSet<>(); - var tp = new TriplePattern("?s :prop ?o"); - graphPattern.add(tp); - - Set ruleNodes = new HashSet<>(); - var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); - ruleNodes.add(neighbour); - - BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); - - assertFalse(bss.haveAllNeighborsContributed()); - var bs = Util.toBindingSet("s=,o=7"); - var changed = bss.add(neighbour, bs.toTripleVarBindingSet(graphPattern)); - assertTrue(changed); - bs = Util.toBindingSet("s=,o=7"); - changed = bss.add(neighbour, bs.toTripleVarBindingSet(graphPattern)); - assertFalse(changed); - assertTrue(bss.haveAllNeighborsContributed()); - } - - @Test - public void testAddingDifferentNonEmptyBindingSets() { - Set graphPattern = new HashSet<>(); - var tp = new TriplePattern("?s :prop ?o"); - graphPattern.add(tp); - - Set ruleNodes = new HashSet<>(); - var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); - ruleNodes.add(neighbour); - - BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); - - assertFalse(bss.haveAllNeighborsContributed()); - var bs = Util.toBindingSet("s=,o=7"); - var changed = bss.add(neighbour, bs.toTripleVarBindingSet(graphPattern)); - assertTrue(changed); - bs = Util.toBindingSet("s=,o=8"); - changed = bss.add(neighbour, bs.toTripleVarBindingSet(graphPattern)); - assertTrue(changed); - assertTrue(bss.haveAllNeighborsContributed()); - } + @Test + public void testAddingEmptyBindingSetTwice() { + Set graphPattern = new HashSet<>(); + var tp = new TriplePattern("?s :prop ?o"); + graphPattern.add(tp); + + Set ruleNodes = new HashSet<>(); + var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); + ruleNodes.add(neighbour); + + BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); + + assertFalse(bss.haveAllNeighborsContributed()); + TripleVarBindingSet tripleVarBindingSet = new BindingSet().toTripleVarBindingSet(graphPattern); + + var changed = bss.add(neighbour, convertToDummyMap(tripleVarBindingSet)); + assertTrue(changed); + + TripleVarBindingSet tripleVarBindingSet2 = new BindingSet().toTripleVarBindingSet(graphPattern); + + changed = bss.add(neighbour, convertToDummyMap(tripleVarBindingSet2)); + assertFalse(changed); + + assertTrue(bss.haveAllNeighborsContributed()); + } + + private Map convertToDummyMap(TripleVarBindingSet aTVBS) { + var map = new HashMap(); + map.put(new Match(new TriplePattern(""), new TriplePattern(""), new HashMap<>()), aTVBS); + return map; + } + + @Test + public void testAddingNonEmptyBindingSetTwice() { + Set graphPattern = new HashSet<>(); + var tp = new TriplePattern("?s :prop ?o"); + graphPattern.add(tp); + + Set ruleNodes = new HashSet<>(); + var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); + ruleNodes.add(neighbour); + + BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); + + assertFalse(bss.haveAllNeighborsContributed()); + var bs = Util.toBindingSet("s=,o=7"); + var changed = bss.add(neighbour, convertToDummyMap(bs.toTripleVarBindingSet(graphPattern))); + assertTrue(changed); + bs = Util.toBindingSet("s=,o=7"); + changed = bss.add(neighbour, convertToDummyMap(bs.toTripleVarBindingSet(graphPattern))); + assertFalse(changed); + assertTrue(bss.haveAllNeighborsContributed()); + } + + @Test + public void testAddingDifferentNonEmptyBindingSets() { + Set graphPattern = new HashSet<>(); + var tp = new TriplePattern("?s :prop ?o"); + graphPattern.add(tp); + + Set ruleNodes = new HashSet<>(); + var neighbour = new ActiveConsRuleNode(new Rule(new HashSet<>(), new HashSet<>(Arrays.asList(tp)))); + ruleNodes.add(neighbour); + + BindingSetStore bss = new BindingSetStore(graphPattern, ruleNodes); + + assertFalse(bss.haveAllNeighborsContributed()); + var bs = Util.toBindingSet("s=,o=7"); + var changed = bss.add(neighbour, convertToDummyMap(bs.toTripleVarBindingSet(graphPattern))); + assertTrue(changed); + bs = Util.toBindingSet("s=,o=8"); + changed = bss.add(neighbour, convertToDummyMap(bs.toTripleVarBindingSet(graphPattern))); + assertTrue(changed); + assertTrue(bss.haveAllNeighborsContributed()); + } } diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestGraphPatternMatch.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestGraphPatternMatch.java index 3a6177428..7a3a0c684 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestGraphPatternMatch.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestGraphPatternMatch.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -19,6 +20,7 @@ import eu.knowledge.engine.reasoner.Rule; import eu.knowledge.engine.reasoner.SinkBindingSetHandler; import eu.knowledge.engine.reasoner.TransformBindingSetHandler; +import eu.knowledge.engine.reasoner.BaseRule.CombiMatch; import eu.knowledge.engine.smartconnector.impl.Util; public class TestGraphPatternMatch { @@ -176,10 +178,28 @@ public CompletableFuture handle( }); // assertTrue(GraphPatternMatcher.areIsomorphic(gp1, gp2)); - Map> isomorphism = BaseRule.getMatches(anteRule, Collections.singleton(consRule), true, - MatchStrategy.ENTRY_LEVEL.toConfig(true)); + Map> isomorphism = convertToMapping(BaseRule.getMatches(anteRule, + Collections.singleton(consRule), true, MatchStrategy.ENTRY_LEVEL.toConfig(true))); return isomorphism.containsKey(consRule) ? isomorphism.get(consRule) : new HashSet(); } + // copied from RuleStore#convertToMapping + private Map> convertToMapping(Set someMatches) { + var mapping = new HashMap>(); + + for (CombiMatch cm : someMatches) { + for (Map.Entry> entry : cm.entrySet()) { + // get rule match set + var ruleMatchSet = mapping.get(entry.getKey()); + if (ruleMatchSet == null) { + ruleMatchSet = new HashSet(); + mapping.put(entry.getKey(), ruleMatchSet); + } + ruleMatchSet.addAll(entry.getValue()); + } + } + return mapping; + } + } From 7c67b1505a8152c7f6511be8e3fe4f839d2a220b Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 24 Jun 2025 11:19:45 +0200 Subject: [PATCH 4/9] Fix some tests. --- .../knowledge/engine/reasoner/BindingTest.java | 2 +- .../engine/reasoner/api/MatchTest.java | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/BindingTest.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/BindingTest.java index 444930684..5ba7537dc 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/BindingTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/BindingTest.java @@ -61,7 +61,7 @@ public void testFruitfulVsActualMatches() { BaseRule r = new BaseRule("test", toTriplePattern(gp2), new HashSet<>()); - Set matches = r.antecedentMatches(toTriplePattern(gp1), EnumSet.of(MatchFlag.ONLY_BIGGEST)); + Set matches = r.antecedentMatches(toTriplePattern(gp1), EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.ONE_TO_ONE)); LOG.info("matches size: {}", matches.size()); 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 fcc860c88..39184cc49 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 @@ -342,7 +342,7 @@ public void testGPMatcher12() { Set findMatchesWithAntecedent = r.antecedentMatches( new HashSet<>(Arrays.asList(/* t1, */ t5, t9, t8, t7, t6, t4, t3)), - EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED)); + EnumSet.of(MatchFlag.ONLY_BIGGEST, MatchFlag.FULLY_COVERED, MatchFlag.ONE_TO_ONE)); System.out.println("Size: " + findMatchesWithAntecedent.size()); // System.out.println(findMatchesWithConsequent); @@ -430,7 +430,7 @@ public void testGPMatcherCardinalityTest() { Rule r = new Rule(new HashSet<>(), obj); Set findMatchesWithConsequent = r.consequentMatches(new HashSet<>(Arrays.asList(graphPattern)), - EnumSet.noneOf(MatchFlag.class)); + EnumSet.of(MatchFlag.ONE_TO_ONE)); System.out.println("graph pattern size " + gpSize + " gives matches size " + findMatchesWithConsequent.size() + "-" + getNumberOfMatches(gpSize)); @@ -546,13 +546,16 @@ public void testTranslateEmptyBindingSet() { var match = new Match(t1, t2, map); - TripleVarBindingSet tvbs2 = tvbs1 - .translate(new HashSet<>(Arrays.asList(t2)), new HashSet<>(Arrays.asList(match))).values().iterator() - .next(); + Map translated = tvbs1.translate(new HashSet<>(Arrays.asList(t2)), + new HashSet<>(Arrays.asList(match))); + + for (TripleVarBindingSet aBS : translated.values()) { + tvbs1 = tvbs1.merge(aBS); + } - System.out.println("BindingSet: " + tvbs2); + System.out.println("BindingSet: " + tvbs1); - assertTrue(tvbs2.isEmpty()); + assertTrue(tvbs1.isEmpty()); } @Test From 847b0635c9f7a630ff18fdf8fdb638ba9f785fdd Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 24 Jun 2025 11:28:30 +0200 Subject: [PATCH 5/9] Fix test. --- .../java/eu/knowledge/engine/reasoner/api/MatchTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 39184cc49..8825a2091 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 @@ -486,7 +486,7 @@ public void testBugTranslate() { var tvbs = new TripleVarBindingSet(obj, bs); - var nBs = tvbs.translate(rhs, findMatchesWithConsequent); + var nBs = tvbs.translate(rhs, findMatchesWithConsequent).values().iterator().next(); System.out.println(nBs); assertTrue(nBs.isEmpty()); @@ -715,7 +715,8 @@ public void testNewGPMatcherTransitivity() { BaseRule r2 = new Rule(new HashSet<>(), tp2); - var matches = BaseRule.getMatches(r1, new HashSet<>(Arrays.asList(r2)), true, EnumSet.noneOf(MatchFlag.class)); + var matches = BaseRule.getMatches(r1, new HashSet<>(Arrays.asList(r2)), true, + EnumSet.of(MatchFlag.ONLY_BIGGEST)); System.out.println(matches); assertEquals(1, matches.size()); From 520b6efab2ff4ec88bc99af8fbefde6b6897438a Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 24 Jun 2025 15:41:03 +0200 Subject: [PATCH 6/9] Got all unit tests working again! Fixed a bug in the translate method that probably got mitigated by the generic merge that we did over every binding set. When we disabled this and really relied on the combi matches, it started to go wrong. --- .../knowledge/engine/reasoner/api/TripleVarBindingSet.java | 3 +-- .../engine/reasoner/rulenode/BindingSetStore.java | 7 ++++--- .../java/eu/knowledge/engine/reasoner/api/MatchTest.java | 4 ++-- .../engine/reasoner/reasoningnode/TestBindingSetStore.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java index 66b228c8f..f8c11ce25 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java @@ -333,9 +333,8 @@ public Map translate(Set aGraphPatter if (toB.containsVar((Var) toTVar.node) && !toB.getVarValue((Var) toTVar.node).equals(fromTNode.node)) { skip = true; - } else if (!toB.containsVar((Var) toTVar.node)) { + } else toB.put(toTVar, fromTNode.node); - } } } } 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 50b89d884..606035e61 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 @@ -120,7 +120,7 @@ private TripleVarBindingSet translateWithCombiMatches(Set aGraphP if (matchToBS != null) { for (Match cSingleMatch : cEntry.getValue()) { TripleVarBindingSet tvbs = matchToBS.get(cSingleMatch); - + if (tvbs != null) cMatchTVBS.addAll(cMatchTVBS.merge2(tvbs).getBindings()); } @@ -157,8 +157,9 @@ public TripleVarBindingSet get() { } // unfortunately, due to the asymmetry introduced with combi matches (i.e. combi - // matches are available at the antecedent neighbours, but not at the consequent - // neighbors) we use the older (slower) method when there are no combi matches. + // matches are only available at the antecedent/consequent neighbours, but not + // at the consequent/antecedent neighbors depending on backward/forward + // reasoning) we use the older (slower) method when there are no combi matches. if (this.combiMatches != null) this.cache = this.translateWithCombiMatches(this.graphPattern, this.combiMatches, this.neighborBindingSet); else { 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 8825a2091..e16ac5318 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 @@ -699,7 +699,7 @@ public void testNewGPMatcher1() { System.out.println(matches); - assertEquals(matches.size(), 1); + assertEquals(matches.size(), 2); } @Test @@ -740,7 +740,7 @@ public void testNewGPMatcher2() { var matches = BaseRule.getMatches(r1, new HashSet<>(Arrays.asList(r2)), true, EnumSet.noneOf(MatchFlag.class)); System.out.println(matches); - assertEquals(1, matches.size()); + assertEquals(7, matches.size()); } } diff --git a/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java b/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java index dd1f87116..4b67a3329 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/reasoningnode/TestBindingSetStore.java @@ -51,7 +51,7 @@ public void testAddingEmptyBindingSetTwice() { private Map convertToDummyMap(TripleVarBindingSet aTVBS) { var map = new HashMap(); - map.put(new Match(new TriplePattern(""), new TriplePattern(""), new HashMap<>()), aTVBS); + map.put(new Match(new TriplePattern("?s ?p ?o"), new TriplePattern("?a ?b ?c"), new HashMap<>()), aTVBS); return map; } From 6bb2c3e68add262381044126cbb656a711c93170 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Tue, 24 Jun 2025 16:45:50 +0200 Subject: [PATCH 7/9] Tune timeout of test. --- .../smartconnector/misc/WireMockFirstConfigurationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java index 13713940f..fe091da6e 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/misc/WireMockFirstConfigurationTest.java @@ -55,7 +55,7 @@ public static void before(WireMockRuntimeInfo wmRuntimeInfo) { * @throws InterruptedException */ @Test - @Timeout(value = 3, unit = TimeUnit.SECONDS) + @Timeout(value = 4, unit = TimeUnit.SECONDS) public void testConfigHttpConnectTimeout() throws Exception { stubFor(post("/ker/").willReturn(status(201).withBody("{}"))); System.setProperty(SmartConnectorConfig.CONF_KEY_KE_HTTP_TIMEOUT, "1"); From d23d3aee7f625ba1907457b583ddbc5d5314ced9 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 26 Jun 2025 12:20:08 +0200 Subject: [PATCH 8/9] Enable disabled tests again. --- .../engine/reasoner/JenaRDFSRulesTest.java | 2 +- .../reasoner/api/ReasoningPlanTest.java | 32 +++++++++---------- .../api/TestAskAnswerLargeBindingSets.java | 8 +++-- 3 files changed, 22 insertions(+), 20 deletions(-) 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 f0b8424f6..59d6c22c0 100644 --- a/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java +++ b/reasoner/src/test/java/eu/knowledge/engine/reasoner/JenaRDFSRulesTest.java @@ -47,7 +47,7 @@ public class JenaRDFSRulesTest { * @throws ExecutionException * @throws ParseException */ - @Disabled + @Test public void test() throws InterruptedException, ExecutionException, ParseException { 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 99f977064..21cff1cf2 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 @@ -15,6 +15,7 @@ import eu.knowledge.engine.reasoner.ProactiveRule; 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; @@ -26,7 +27,6 @@ public class ReasoningPlanTest { private static final String TEST_RULES = "/reasoningplantest.rls"; - @Disabled("Until optimize() method is availble.") @Test public void test() throws IOException, InterruptedException, ExecutionException { @@ -38,27 +38,24 @@ public void test() throws IOException, InterruptedException, ExecutionException Rule first = someRules.get(0); first.backwardForwardBindingSetHandler = new DataBindingSetHandler(new Table(new String[] { - //@formatter:off + // @formatter:off "a", "b", "c" - //@formatter:on + // @formatter:on }, new String[] { - //@formatter:off - ",,", - ",,", - //@formatter:on + // @formatter:off + ",,", ",,", + // @formatter:on })); Rule second = someRules.get(1); second.backwardForwardBindingSetHandler = new DataBindingSetHandler(new Table(new String[] { - //@formatter:off + // @formatter:off "x", "y", "z" - //@formatter:on + // @formatter:on }, new String[] { - //@formatter:off - ",,", - ",,", - ",,", - //@formatter:on + // @formatter:off + ",,", ",,", ",,", + // @formatter:on })); store.addRules(new HashSet<>(someRules)); @@ -90,9 +87,12 @@ public void test() throws IOException, InterruptedException, ExecutionException aBindingSet.add(b1); aBindingSet.add(b2); - plan.execute(aBindingSet); + TaskBoard tb; + while ((tb = plan.execute(aBindingSet)).hasTasks()) { + tb.executeScheduledTasks(); + } - BindingSet bs = null; //plan.getStartNode().getIncomingAntecedentBindingSet().toBindingSet(); + BindingSet bs = plan.getResults(); System.out.println(bs); assertEquals(2, bs.size()); diff --git a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java index 9cd39b19e..e4d543b22 100644 --- a/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java +++ b/smart-connector/src/test/java/eu/knowledge/engine/smartconnector/api/TestAskAnswerLargeBindingSets.java @@ -36,6 +36,8 @@ public class TestAskAnswerLargeBindingSets { private static BindingSet kb1BS; private static BindingSet kb2BS; + private static int NR_OF_BINDINGS = 1500; + private static char[] chars = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g' }; @BeforeAll @@ -44,7 +46,7 @@ public static void setup() throws InterruptedException, BrokenBarrierException, kb1BS = new BindingSet(); Binding b; - for (int i = 0; i < 350; i++) { + for (int i = 0; i < NR_OF_BINDINGS; i++) { b = new Binding(); for (char c : chars) b.put(Character.toString(c), ""); @@ -53,7 +55,7 @@ public static void setup() throws InterruptedException, BrokenBarrierException, kb2BS = new BindingSet(); - for (int i = 350; i < 700; i++) { + for (int i = NR_OF_BINDINGS; i < (2 * NR_OF_BINDINGS); i++) { b = new Binding(); for (char c : chars) b.put(Character.toString(c), ""); @@ -133,7 +135,7 @@ public void testAskAnswer() throws InterruptedException { assertEquals(new HashSet(Arrays.asList(kb1.getKnowledgeBaseId(), kb3.getKnowledgeBaseId())), kbIds, "The result should come from kb1, kb3 and not: " + kbIds); - assertEquals(700, bindings.size()); + assertEquals(2 * NR_OF_BINDINGS, bindings.size()); } catch (InterruptedException | ExecutionException e) { fail(); From d35098d2f60a5134590890adad8b5feef0450264 Mon Sep 17 00:00:00 2001 From: Barry Nouwt Date: Thu, 26 Jun 2025 17:11:53 +0200 Subject: [PATCH 9/9] Rename one of two merge methods and remove duplicate code. --- .../reasoner/api/TripleVarBindingSet.java | 47 ++----------------- .../reasoner/rulenode/BindingSetStore.java | 2 +- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java index f8c11ce25..039cb5e25 100644 --- a/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java +++ b/reasoner/src/main/java/eu/knowledge/engine/reasoner/api/TripleVarBindingSet.java @@ -163,48 +163,9 @@ public String toString() { */ public TripleVarBindingSet merge(TripleVarBindingSet aGraphBindingSet) { - LOG.trace("Merging {} bindings with our {} bindings.", aGraphBindingSet.getBindings().size(), - this.getBindings().size()); - - TripleVarBindingSet gbs = new TripleVarBindingSet(this.graphPattern); - - final int otherBindingSetSize = aGraphBindingSet.getBindings().size(); - final long totalCount = (long) otherBindingSetSize * (long) this.getBindings().size(); - if (totalCount > LARGE_BS_SIZE) - LOG.warn("Merging 2 large BindingSets ({} * {} = {}). This can take some time.", - aGraphBindingSet.getBindings().size(), this.getBindings().size(), totalCount); - - if (this.bindings.isEmpty()) { - for (TripleVarBinding tvb2 : aGraphBindingSet.getBindings()) { - gbs.add(tvb2); - } - } else { - // Cartesian product is the base case - gbs.addAll(aGraphBindingSet.getBindings()); - gbs.addAll(this.bindings); - AtomicLong progress = new AtomicLong(0); - - final int milestoneSize = PROGRESS_MILESTONE_SIZE; - AtomicLong nextMilestone = new AtomicLong(milestoneSize); - - this.bindings.stream().parallel().forEach(tvb1 -> { - for (TripleVarBinding otherB : aGraphBindingSet.getBindings()) { - // always add a merged version of the two bindings, except when they conflict. - if (!tvb1.isConflicting(otherB)) { - gbs.add(tvb1.merge(otherB)); - } - } - final long current = progress.incrementAndGet(); - - if (totalCount > LARGE_BS_SIZE && current == nextMilestone.get()) { - LOG.trace("{}/{} BindingSet merge tasks done!", current * otherBindingSetSize, totalCount); - nextMilestone.set(current + milestoneSize); - } - }); - } - - if (totalCount > LARGE_BS_SIZE) - LOG.debug("Merging large BindingSets done!"); + TripleVarBindingSet gbs = combine(aGraphBindingSet); + gbs.addAll(aGraphBindingSet.getBindings()); + gbs.addAll(this.bindings); return gbs; } @@ -218,7 +179,7 @@ public TripleVarBindingSet merge(TripleVarBindingSet aGraphBindingSet) { * @apiNote Note that this method does not modify the original (this) binding * set. */ - public TripleVarBindingSet merge2(TripleVarBindingSet aGraphBindingSet) { + public TripleVarBindingSet combine(TripleVarBindingSet aGraphBindingSet) { LOG.trace("Merging {} bindings with our {} bindings.", aGraphBindingSet.getBindings().size(), this.getBindings().size()); 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 606035e61..dd5d5c983 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 @@ -122,7 +122,7 @@ private TripleVarBindingSet translateWithCombiMatches(Set aGraphP TripleVarBindingSet tvbs = matchToBS.get(cSingleMatch); if (tvbs != null) - cMatchTVBS.addAll(cMatchTVBS.merge2(tvbs).getBindings()); + cMatchTVBS.addAll(cMatchTVBS.combine(tvbs).getBindings()); } } }