diff --git a/integraal/integraal-model/src/main/java/fr/boreal/model/logicalElements/api/TermCompound.java b/integraal/integraal-model/src/main/java/fr/boreal/model/logicalElements/api/TermCompound.java index 71b18ef4e5a593a16dc9d9ef8af4c613bb9f51ba..f54f651af7810acba6233ed328bf4679062a9892 100644 --- a/integraal/integraal-model/src/main/java/fr/boreal/model/logicalElements/api/TermCompound.java +++ b/integraal/integraal-model/src/main/java/fr/boreal/model/logicalElements/api/TermCompound.java @@ -56,7 +56,9 @@ public interface TermCompound { return this.getTerms() .flatMap(t -> { if (t instanceof TermCompound tc) { - return tc.getAllNestedTerms(); + return Stream.concat( + tc.getAllNestedTerms(), + Stream.of(t)); } return Stream.of(t); }) @@ -70,15 +72,8 @@ public interface TermCompound { * class type filters the terms by type */ default <T extends Term> Stream<T> getNestedTerms(Class<T> classType) { - return (Stream<T>) this.getTerms() - .flatMap(t -> { - if (t instanceof TermCompound tc) { - return tc.getNestedTerms(classType); - } else if (classType.isInstance(t)) { - return Stream.of(t); - } - return Stream.empty(); - }) + return (Stream<T>) this.getAllNestedTerms() + .filter(t -> classType.isInstance(t)) .distinct(); } } diff --git a/integraal/integraal-model/src/main/java/fr/boreal/model/queryEvaluation/api/QueryEvaluator.java b/integraal/integraal-model/src/main/java/fr/boreal/model/queryEvaluation/api/QueryEvaluator.java index 30a8950382d4b1bb0a9963cca3bdcf77cb1ea92a..de685949fbf49ca20be23d7060a2a35b2b37b639 100644 --- a/integraal/integraal-model/src/main/java/fr/boreal/model/queryEvaluation/api/QueryEvaluator.java +++ b/integraal/integraal-model/src/main/java/fr/boreal/model/queryEvaluation/api/QueryEvaluator.java @@ -5,14 +5,16 @@ import fr.boreal.model.data.readable.exception.EvaluationException; import fr.boreal.model.kb.api.FactBase; import fr.boreal.model.logicalElements.api.Substitution; import fr.boreal.model.logicalElements.api.Variable; -import fr.boreal.model.logicalElements.functional.AllVariablesInSubstitutionMapToConstant; import fr.boreal.model.logicalElements.functional.SpecificVariablesInSubstitutionMapToConstant; import fr.boreal.model.logicalElements.impl.SubstitutionImpl; import fr.boreal.model.query.api.Query; import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.iterators.FilterIterator; -import java.util.*; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -32,12 +34,17 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * @param query query to evaluate * @param queryableData readable data source in which atoms are stored * @param variablesThatMustBeMappedToConstants the set of variables that must be mapped to constants - * @param preHomomorphism a partial homomorphism from var(query) to terms(readable data source) to extend + * @param preHomomorphism a partial homomorphism from var(query) to terms(readable data source) + * to extend * @return an {@link Iterator} of {@link Substitution} over all the answers of * the given query in the given readable data source with respect to the query * answer variables. */ - Stream<Substitution> evaluate(Q query, QD queryableData, Collection<Variable> variablesThatMustBeMappedToConstants, Substitution preHomomorphism) throws EvaluationException; + Stream<Substitution> evaluate( + Q query, + QD queryableData, + Collection<Variable> variablesThatMustBeMappedToConstants, + Substitution preHomomorphism) throws EvaluationException; /** * @param query query to evaluate @@ -58,7 +65,8 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * homomorphisms of the given query in the given readable data source with respect * to the query answer variables. */ - default Stream<Substitution> homomorphism(Q query, QD queryableData, Substitution preHomomorphism) throws EvaluationException { + default Stream<Substitution> homomorphism(Q query, QD queryableData, Substitution preHomomorphism) + throws EvaluationException { return evaluate(query, queryableData, Set.of(), preHomomorphism); } @@ -81,7 +89,8 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * answers of the given query in the given readable data source with respect * to the query answer variables. */ - default Stream<Substitution> evaluate(Q query, QD queryableData, Substitution preHomomorphism) throws EvaluationException { + default Stream<Substitution> evaluate(Q query, QD queryableData, Substitution preHomomorphism) + throws EvaluationException { return evaluate(query, queryableData, query.getAnswerVariables(), preHomomorphism); } @@ -91,7 +100,7 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * @return true iff there exist a substitution s that is a homomorphism of the query on the readable data source */ default boolean existHomomorphism(Q query, QD queryableData) throws EvaluationException { - return this.homomorphism(query, queryableData).findAny().isPresent(); + return this.homomorphism(query, queryableData).parallel().findAny().isPresent(); } /** @@ -100,8 +109,9 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * @param preHomomorphism a partial homomorphism to extend * @return true iff there exist a substitution s that is a homomorphism of the query on the readable data source */ - default boolean existHomomorphism(Q query, QD queryableData, Substitution preHomomorphism) throws EvaluationException { - return this.homomorphism(query, queryableData, preHomomorphism).findAny().isPresent(); + default boolean existHomomorphism(Q query, QD queryableData, Substitution preHomomorphism) + throws EvaluationException { + return this.homomorphism(query, queryableData, preHomomorphism).parallel().findAny().isPresent(); } /** @@ -110,7 +120,7 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * @return true iff there exist a substitution s that is an answer to the query on the readable data source */ default boolean existAnswer(Q query, QD queryableData) throws EvaluationException { - return this.evaluate(query, queryableData).findAny().isPresent(); + return this.evaluate(query, queryableData).parallel().findAny().isPresent(); } /** @@ -120,7 +130,7 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * @return true iff there exist a substitution s that is an answer to the query on the readable data source */ default boolean existAnswer(Q query, QD queryableData, Substitution preHomomorphism) throws EvaluationException { - return this.evaluate(query, queryableData, preHomomorphism).findAny().isPresent(); + return this.evaluate(query, queryableData, preHomomorphism).parallel().findAny().isPresent(); } /** @@ -151,7 +161,9 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * filtered by retaining only substitutions mapping to constants if * required */ - default Stream<Substitution> postprocessResult(Stream<Substitution> unfilteredSubstitutions, Collection<Variable> variablesThatMustBeMappedToConstants) { + default Stream<Substitution> postprocessResult( + Stream<Substitution> unfilteredSubstitutions, + Collection<Variable> variablesThatMustBeMappedToConstants) { if (variablesThatMustBeMappedToConstants.isEmpty()) { return unfilteredSubstitutions; } else { @@ -160,24 +172,6 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { } } - /** - * Filters the result of a query by removing substitutions which map a variable - * to some other variable of the active domain. - * - * @param unfilteredSubstitutions the unfiltered substitution - * @param constantsOnly true iff all variables must be mapped to constants - * @return the post-processed query result where results have been possibly - * filtered by retaining only substitutions mapping to constants if - * required - */ - default Stream<Substitution> postprocessResult(Stream<Substitution> unfilteredSubstitutions, boolean constantsOnly) { - if (constantsOnly) { - return postprocessResult(unfilteredSubstitutions, new AllVariablesInSubstitutionMapToConstant()); - } else { - return unfilteredSubstitutions; - } - } - /** * Filters the result of a query according to a predicate. * @@ -187,7 +181,9 @@ public interface QueryEvaluator<Q extends Query, QD extends QueryableData> { * filtered by retaining only substitutions mapping to constants if * required */ - default Stream<Substitution> postprocessResult(Stream<Substitution> unfilteredSubstitutions, Predicate<Substitution> predicate) { + default Stream<Substitution> postprocessResult( + Stream<Substitution> unfilteredSubstitutions, + Predicate<Substitution> predicate) { return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new FilterIterator<>(unfilteredSubstitutions.iterator(), predicate), 0), diff --git a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/FOQueryEvaluators.java b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/FOQueryEvaluators.java index 7c185423e2648c46a6f4274e61d02799eba6fa4d..1f1a2b15d24389077f52284ec7e5ab0749da1b08 100644 --- a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/FOQueryEvaluators.java +++ b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/FOQueryEvaluators.java @@ -2,6 +2,7 @@ package fr.boreal.query_evaluation; import fr.boreal.model.formula.FOFormulas; import fr.boreal.model.formula.api.FOFormula; +import fr.boreal.model.logicalElements.api.Atom; import fr.boreal.model.logicalElements.api.Substitution; import fr.boreal.model.logicalElements.api.Term; import fr.boreal.model.logicalElements.api.Variable; @@ -99,4 +100,21 @@ public class FOQueryEvaluators { return FOQueryEvaluators.rebuildAnswer(this.initialQuery, this.partialAnswers.next()); } } + + public static Map<Integer, Term> computeAtomicPositionsAssignation( + Atom atom, + Substitution preHomomorphism) { + Map<Integer, Term> positions = new HashMap<>(); + + for (int i = 0; i < atom.getTermSequence().size(); i++) { + Term t = atom.getTermSequence().get(i); + if (t.isGround()) { + positions.put(i, t); + } else if (preHomomorphism.keys().contains(t)) { + positions.put(i, preHomomorphism.createImageOf(t)); + } + } + + return positions; + } } diff --git a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/atomic/AtomicFOQueryEvaluator.java b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/atomic/AtomicFOQueryEvaluator.java index d3c5f9dfca313a6cddfb99d71d70bb021582896e..7ef33f31f016eb8607c76e8c01c45626b01abfbe 100644 --- a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/atomic/AtomicFOQueryEvaluator.java +++ b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/atomic/AtomicFOQueryEvaluator.java @@ -10,6 +10,7 @@ import fr.boreal.model.logicalElements.impl.SubstitutionImpl; import fr.boreal.model.partition.TermPartition; import fr.boreal.model.query.api.FOQuery; import fr.boreal.model.queryEvaluation.api.FOQueryEvaluator; +import fr.boreal.query_evaluation.FOQueryEvaluators; import fr.boreal.query_evaluation.utils.filters.GroundTermAtPositionFilter; import fr.boreal.query_evaluation.utils.filters.PositionsAssignationFilter; import fr.boreal.query_evaluation.utils.transformers.TermSequenceToSubstitutionTransformer; @@ -67,7 +68,8 @@ public class AtomicFOQueryEvaluator<QD extends QueryableData> implements FOQuery return t; }}).toList()); - Map<Integer, Term> positionsAffectation = computePositionsAssignation(atom, preHomomorphism); + Map<Integer, Term> positionsAffectation = + FOQueryEvaluators.computeAtomicPositionsAssignation(atom, preHomomorphism); BasicQuery bq = selectBasicQuery(queryableData, atom.getPredicate(), positionsAffectation).orElseThrow( () -> new EvaluationException(String.format( "The query %s is unsupported by the QueryableData %s", @@ -95,23 +97,6 @@ public class AtomicFOQueryEvaluator<QD extends QueryableData> implements FOQuery .map(s -> makeSubstitutionOnAnswerVariables(s, query.getAnswerVariables(), query.getVariableEqualities())); } - private Map<Integer, Term> computePositionsAssignation( - Atom atom, - Substitution preHomomorphism) { - Map<Integer, Term> positions = new HashMap<>(); - - for (int i = 0; i < atom.getTermSequence().size(); i++) { - Term t = atom.getTermSequence().get(i); - if (t.isGround()) { - positions.put(i, t); - } else if (preHomomorphism.keys().contains(t)) { - positions.put(i, preHomomorphism.createImageOf(t)); - } - } - - return positions; - } - private static Substitution makeSubstitutionOnAnswerVariables ( Substitution result, Collection<Variable> answerVariables, diff --git a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/BacktrackEvaluator.java b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/BacktrackEvaluator.java index 8efca40766f5a0f907da0c0b7260c5e030b8ea80..44e7bb932591c9615cfcec66c560ccc956cc1bbe 100644 --- a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/BacktrackEvaluator.java +++ b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/BacktrackEvaluator.java @@ -218,7 +218,9 @@ public class BacktrackEvaluator<QD extends QueryableData> implements FOQueryEval } else { // going down if (this.scheduler.hasNext(this.level)) { - FOFormula element = this.scheduler.next(this.level, this.solutionManager.getCurrentSolution()); + FOFormula element = this.scheduler.next( + this.level, + this.solutionManager.getCurrentSolution()); // answer variables = (general U join) \ already affected Collection<Variable> subquery_vars = new HashSet<>(); subquery_vars.addAll(this.query.getAnswerVariables()); @@ -227,13 +229,19 @@ public class BacktrackEvaluator<QD extends QueryableData> implements FOQueryEval // keep variables from (1) the formula and (2) the equalities with an element of the formula Set<Variable> variables = element.getVariables().collect(Collectors.toSet()); subquery_vars.removeIf(v -> variables.contains(v) && - this.query.getVariableEqualities().getClass(v).stream().noneMatch(variables::contains)); + this.query.getVariableEqualities().getClass(v).stream() + .noneMatch(variables::contains)); subquery_vars.retainAll(variables); - Optional<Substitution> previous_step_subst_opt = this.preHomomorphism.merged(this.solutionManager.getCurrentSolution()); + Optional<Substitution> previous_step_subst_opt = + this.preHomomorphism.merged(this.solutionManager.getCurrentSolution()); if (previous_step_subst_opt.isPresent()) { Substitution subquery_subst = previous_step_subst_opt.get(); - FOQuery<? extends FOFormula> subquery = FOQueryFactory.instance().createOrGetQuery(element, subquery_vars, this.query.getVariableEqualities()); - Iterator<Substitution> sub_results = this.evaluator.evaluate(subquery, this.queryableData, this.vars, subquery_subst).iterator(); + FOQuery<? extends FOFormula> subquery = + FOQueryFactory.instance().createOrGetQuery( + element, subquery_vars, this.query.getVariableEqualities()); + Iterator<Substitution> sub_results = + this.evaluator.evaluate(subquery, this.queryableData, this.vars, subquery_subst) + .iterator(); if (sub_results.hasNext()) { this.solutionManager.add(this.level, sub_results); if (this.solutionManager.next(this.level)) { diff --git a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/NaiveDynamicScheduler.java b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/NaiveDynamicScheduler.java index 985f45cc486617161c41e917a3197293f8dfb103..80cb55454d79ff037465954ebdec45104dc40f79 100644 --- a/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/NaiveDynamicScheduler.java +++ b/integraal/integraal-query-evaluation/src/main/java/fr/boreal/query_evaluation/conjunction/backtrack/NaiveDynamicScheduler.java @@ -1,15 +1,21 @@ package fr.boreal.query_evaluation.conjunction.backtrack; +import com.sun.jdi.InvalidLineNumberException; import fr.boreal.model.data.readable.QueryableData; +import fr.boreal.model.data.readable.query.AtomicPattern; +import fr.boreal.model.data.readable.query.BasicQuery; import fr.boreal.model.formula.api.FOFormula; import fr.boreal.model.formula.api.FOFormulaConjunction; import fr.boreal.model.formula.api.FOFormulaDisjunction; import fr.boreal.model.formula.api.FOFormulaNegation; import fr.boreal.model.logicalElements.api.*; import fr.boreal.model.query.api.FOQuery; +import fr.boreal.query_evaluation.FOQueryEvaluators; +import org.eclipse.rdf4j.query.algebra.Var; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; public class NaiveDynamicScheduler implements Scheduler { @@ -19,6 +25,8 @@ public class NaiveDynamicScheduler implements Scheduler { private final Map<FormulaWrapper, Set<Variable>> sharedVariables; private final Map<FormulaWrapper, Boolean> containsFunctionalTerm; private final Map<FormulaWrapper, Set<Variable>> functionalTermVariables; + private final Map<FormulaWrapper, Set<Variable>> mandatoryVariables; + private final Set<Predicate> predicateWithTermFilter; /** * Creates a new scheduler over the given query and fact base @@ -27,11 +35,14 @@ public class NaiveDynamicScheduler implements Scheduler { */ public NaiveDynamicScheduler(FOQuery<? extends FOFormulaConjunction> query, QueryableData queryableData) { this.queryableData = queryableData; - this.notOrdered = new ArrayList<>(); - this.ordered = new ArrayList<>(); + int numberSubElements = query.getFormula().getSubElements().size(); + this.notOrdered = new ArrayList<>(numberSubElements); + this.ordered = new ArrayList<>(numberSubElements); this.sharedVariables = new HashMap<>(); this.containsFunctionalTerm = new HashMap<>(); this.functionalTermVariables = new HashMap<>(); + this.mandatoryVariables = new HashMap<>(); + this.predicateWithTermFilter = new HashSet<>(); // Populate notOrdered list with initial formulas for (FOFormula formula : query.getFormula().getSubElements()) { @@ -42,6 +53,10 @@ public class NaiveDynamicScheduler implements Scheduler { if (containsFunctionalTerm.get(formulaWrapper)) { functionalTermVariables.put(formulaWrapper, computeFunctionalTermVariables(formula)); } + mandatoryVariables.put(formulaWrapper, computeMandatoryVariables(formula)); + if (formula instanceof Atom a && computePredicateWithTermFilter(a.getPredicate())) { + this.predicateWithTermFilter.add(a.getPredicate()); + } } // Perform static sorting @@ -91,21 +106,18 @@ public class NaiveDynamicScheduler implements Scheduler { } private boolean computeContainsFunctionalTerm(FOFormula formula) { - return formula.getNestedTerms(EvaluableFunction.class).findAny().isPresent(); + boolean result = formula + .getNestedTerms(EvaluableFunction.class) + .findAny() + .isPresent(); + return result; } private Set<Variable> computeFunctionalTermVariables(FOFormula formula) { return switch (formula) { - case Atom atom -> { - Set<Variable> variables = new HashSet<>(); - for (Term term : atom.getTermSequence()) { - if (term.isEvaluableFunction()) { - EvaluableFunction fterm = (EvaluableFunction) term; - fterm.getNestedTerms(Variable.class).forEach(variables::add); - } - } - yield variables; - } + case Atom atom -> atom.getNestedTerms(EvaluableFunction.class) + .flatMap(ef -> ef.getNestedTerms(Variable.class)) + .collect(Collectors.toSet()); case FOFormulaNegation neg -> computeFunctionalTermVariables(neg.element()); case FOFormulaConjunction conj -> conj.getSubElements().stream() .flatMap(f -> this.computeFunctionalTermVariables(f).stream()).collect(Collectors.toSet()); @@ -115,8 +127,25 @@ public class NaiveDynamicScheduler implements Scheduler { }; } + private Set<Variable> computeMandatoryVariables(FOFormula formula) { + Set<Variable> variables = new HashSet<>(); + for (Atom a : formula.asAtomSet()) { + Set<Integer> mandatoryPositions = + this.queryableData.getBasicPattern(a.getPredicate()).getMandatoryPositions(); + for (Integer mandatoryPosition : mandatoryPositions) { + Term t = a.getTerm(mandatoryPosition); + if (t instanceof Variable v) { + variables.add(v); + } else if (t instanceof TermCompound tc) { + tc.getNestedTerms(Variable.class).forEach(variables::add); + } + } + } + + return variables; + } + private boolean containsFunctionalTerm(Atom atom) { - //return Arrays.stream(atom.getTerms()).anyMatch(Term::isEvaluableFunction); return containsFunctionalTerm.get(new FormulaWrapper(atom)); } @@ -124,6 +153,34 @@ public class NaiveDynamicScheduler implements Scheduler { return formula.getConstants().count(); } + private Optional<Long> estimateBound(Atom atom, Substitution currentSolution) { + long minBound = Long.MAX_VALUE; + AtomicPattern atomicPattern = queryableData.getBasicPattern(atom.getPredicate()); + + Map<Integer, Term> positionsAffectation = + FOQueryEvaluators.computeAtomicPositionsAssignation(atom, currentSolution); + for (BasicQuery bq: (Iterable<BasicQuery>) + atomicPattern.createQueries(positionsAffectation)::iterator) { + Optional<Long> estimation = queryableData.estimateBound(bq); + if (estimation.isPresent()) { + if (estimation.get() < minBound) { + minBound = estimation.get(); + } + } + } + + return minBound == Long.MAX_VALUE ? Optional.empty() : Optional.of(minBound); + } + + private boolean canFilterWithTerms(Predicate p) { + return predicateWithTermFilter.contains(p); + } + + private boolean computePredicateWithTermFilter(Predicate p) { + return this.queryableData.getBasicPattern(p).getIndexablePatterns().stream() + .anyMatch(s -> !s.equals(Set.of())); + } + @Override public FOFormula next(int level, Substitution currentSolution) { // Ensure previous levels are cleared @@ -131,13 +188,21 @@ public class NaiveDynamicScheduler implements Scheduler { notOrdered.addLast(ordered.removeLast()); } + // If there is only one element to order, we directly return it if (notOrdered.size() == 1) { ordered.add(notOrdered.removeLast()); return ordered.getLast(); } - // Prioritize evaluable computed atoms + // Prioritize evaluable computed atoms and atomic negation. + // The idea is that we could backtrack quicker if the evaluation is false for (FOFormula formula : notOrdered) { + // Consider only formulas where all mandatory variables are assigned + if (!areAllMandatoryVariablesAssigned(formula, currentSolution)) { + continue; + } + + // If formula is an instance of an evaluable computed atom, return it directly if (formula instanceof ComputedAtom computedAtom) { if (isEvaluable(computedAtom, currentSolution)) { notOrdered.remove(formula); @@ -145,6 +210,9 @@ public class NaiveDynamicScheduler implements Scheduler { return formula; } } else if (formula instanceof FOFormulaNegation negation && negation.element() instanceof Atom atom) { + // Otherwise, if the formula is an atomic negation + + // If it is an atomic negation of an evaluable computed atom, return it if (atom instanceof ComputedAtom computedAtom) { if (isEvaluable(computedAtom, currentSolution)) { notOrdered.remove(formula); @@ -152,45 +220,58 @@ public class NaiveDynamicScheduler implements Scheduler { return formula; } } else { + // If it is not a computed atom + // First check if there are evaluable functional terms and if so, are there evaluable? if (containsFunctionalTerm(atom) && !areFunctionalTermsEvaluable(atom, currentSolution)) { continue; } - // TODO: redo this part to take basic patterns into account - /*Optional<Long> estimate = queryableData.estimateMatchCount(atom, currentSolution); + // Do an estimate to know if the subquery has an answer + // If so, we return the negation so that the algorithm will backtrack + Optional<Long> estimate = estimateBound(atom, currentSolution); if (estimate.isPresent() && estimate.get() > 0) { notOrdered.remove(formula); ordered.add(formula); return formula; - }*/ + } } } } - // Dynamic sorting based on fact base information - // TODO: redo this part to take basic patterns into account + // Dynamic sorting based on the estimation of the bound FOFormula bestFormula = null; - /*long bestEstimate = Long.MAX_VALUE; + long bestEstimate = Long.MAX_VALUE; for (FOFormula formula : notOrdered) { + // Consider only formulas where all mandatory variables are assigned + if (!areAllMandatoryVariablesAssigned(formula, currentSolution)) { + continue; + } + + // We consider atomic formulas that are not computed atoms (they are already treated) if (formula instanceof Atom atom && !(atom instanceof ComputedAtom)) { if (containsFunctionalTerm(atom) && !areFunctionalTermsEvaluable(atom, currentSolution)) { continue; } - Optional<Long> estimate = queryableData.estimateMatchCount(atom, currentSolution); + // Try to estimate the bound + Optional<Long> estimate = estimateBound(atom, currentSolution); + // If we have a bound, and we know there is no answer, return the atom so that we backtrack if (estimate.isPresent() && estimate.get() == 0) { notOrdered.remove(formula); ordered.add(formula); return formula; } - if (!queryableData.canPerformIndexedMatch(atom, currentSolution)) { + // If we know that it is not possible to filter the results with the assignations of terms, + // we directly return the atom + if (!canFilterWithTerms(atom.getPredicate())) { notOrdered.remove(formula); ordered.add(formula); return formula; } + // If the estimation for this atom is better than for the previous one, we update the best formula long currentEstimate = estimate.orElse(Long.MAX_VALUE); if (currentEstimate < bestEstimate) { bestEstimate = currentEstimate; @@ -199,12 +280,22 @@ public class NaiveDynamicScheduler implements Scheduler { } } + // If we have an estimation, we return the best atom if (bestFormula != null) { notOrdered.remove(bestFormula); ordered.add(bestFormula); return bestFormula; - } else*/ if (!notOrdered.isEmpty()) { - bestFormula = notOrdered.removeLast(); + } + + // Otherwise, we select any formula that has its mandatory variables assigned + if (!notOrdered.isEmpty()) { + bestFormula = notOrdered.stream() + .filter(f -> areAllMandatoryVariablesAssigned(f, currentSolution)) + .findAny() + .orElseThrow(() -> new RuntimeException(String.format( + "There are no subquery with all mandatory variables assigned: \n" + + "ordered: %s, notOrdered: %s, currentSolution: %s", + ordered, notOrdered, currentSolution))); ordered.add(bestFormula); return bestFormula; } else { @@ -238,6 +329,10 @@ public class NaiveDynamicScheduler implements Scheduler { return currentSolution.keys().containsAll(functionalVars); } + private boolean areAllMandatoryVariablesAssigned(FOFormula formula, Substitution currentSolution) { + return currentSolution.keys().containsAll(mandatoryVariables.get(new FormulaWrapper(formula))); + } + // Wrapper class to use default hashCode and equals based on object reference private record FormulaWrapper(FOFormula formula) { @Override diff --git a/integraal/integraal-query-evaluation/src/main/java/module-info.java b/integraal/integraal-query-evaluation/src/main/java/module-info.java index 81709848f9824b64bc21edd0dd6e63ac5b53f91b..a797271bb404fa7e2274d6b072f80228e12c1d94 100644 --- a/integraal/integraal-query-evaluation/src/main/java/module-info.java +++ b/integraal/integraal-query-evaluation/src/main/java/module-info.java @@ -19,6 +19,7 @@ module fr.boreal.query_evaluation { requires org.slf4j; requires org.hsqldb; requires rdf4j.queryalgebra.model; + requires jdk.jdi; exports fr.boreal.query_evaluation.atomic; exports fr.boreal.query_evaluation.conjunction; diff --git a/integraal/integraal-query-evaluation/src/test/java/fr/boreal/test/query_evaluation/NaiveDynamicSchedulerTest.java b/integraal/integraal-query-evaluation/src/test/java/fr/boreal/test/query_evaluation/NaiveDynamicSchedulerTest.java index 309579d527390dfc7cb7460ecf38402b53677762..4ea70fea4012b22d4d463c10b1a324655c9c5e63 100644 --- a/integraal/integraal-query-evaluation/src/test/java/fr/boreal/test/query_evaluation/NaiveDynamicSchedulerTest.java +++ b/integraal/integraal-query-evaluation/src/test/java/fr/boreal/test/query_evaluation/NaiveDynamicSchedulerTest.java @@ -9,8 +9,10 @@ import fr.boreal.model.formula.factory.FOFormulaFactory; import fr.boreal.model.kb.api.FactBase; import fr.boreal.model.logicalElements.api.Atom; import fr.boreal.model.logicalElements.api.Substitution; +import fr.boreal.model.logicalElements.factory.impl.SameObjectTermFactory; import fr.boreal.model.logicalElements.impl.AtomImpl; import fr.boreal.model.logicalElements.impl.SubstitutionImpl; +import fr.boreal.model.logicalElements.impl.VariableImpl; import fr.boreal.model.partition.TermPartition; import fr.boreal.model.query.api.FOQuery; import fr.boreal.model.query.factory.FOQueryFactory; @@ -117,7 +119,11 @@ class NaiveDynamicSchedulerTest { Assertions.assertTrue(scheduler.hasNext(1)); substitution = GenericFOQueryEvaluator.defaultInstance().evaluate( - FOQueryFactory.instance().createOrGetQuery(firstFormula, List.of()), + FOQueryFactory.instance().createOrGetQuery( + firstFormula, + List.of( + SameObjectTermFactory.instance().createOrGetVariable("X"), + SameObjectTermFactory.instance().createOrGetVariable("Y"))), factBase).iterator().next(); secondFormula = scheduler.next(1, substitution); Assertions.assertTrue(ssumxy.equals(secondFormula) || ssumx.equals(secondFormula)); @@ -134,7 +140,7 @@ class NaiveDynamicSchedulerTest { @Test void testComputedAtom() throws Exception { String dlgpFacts = "p(a, b). q(4)."; - String dlgpQuery = "@prefix ig: <stdfct> ? :- p(X, Y), ig:isEven(ig:sum(X, Y, 7))."; + String dlgpQuery = "@computed ig: <stdfct> ? :- p(X, Y), ig:isEven(ig:sum(X, Y, 7))."; FactBase factBase = new SimpleInMemoryGraphStore(DlgpUtil.parseFacts(dlgpFacts)); FOQuery<FOFormulaConjunction> query = (FOQuery<FOFormulaConjunction>) DlgpUtil.parseQueries(dlgpQuery).iterator().next();