From 372058e54825ab58a66c25ae528e81a656c22659 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Tue, 7 Mar 2023 16:26:26 +0100 Subject: feat: terms and improved query evaluation * Implement data terms for computations in queries. * Function-like queries with computed results. * Improved query evaluation, including positive and negative diagonal cosntraints. * Preliminary local search support. * Changes to the DNF representation for count and aggregation support. feat: terms wip feat: query terms wip feat: query evaluation, diagonal constraints, local search wip fix reasoning compilation wip --- .../query/viatra/ViatraModelQueryBuilder.java | 11 +- .../store/query/viatra/ViatraTupleLike.java | 18 - .../internal/ViatraModelQueryAdapterImpl.java | 51 +- .../internal/ViatraModelQueryBuilderImpl.java | 63 ++- .../internal/ViatraModelQueryStoreAdapterImpl.java | 22 +- .../context/RelationalQueryMetaContext.java | 19 + .../internal/context/RelationalRuntimeContext.java | 31 +- .../localsearch/ExtendOperationExecutor.java | 75 +++ .../localsearch/ExtendPositivePatternCall.java | 116 ++++ .../internal/localsearch/FlatCostFunction.java | 30 + .../internal/localsearch/GenericTypeExtend.java | 136 +++++ .../RelationalLocalSearchBackendFactory.java | 55 ++ .../RelationalLocalSearchResultProvider.java | 23 + .../localsearch/RelationalOperationCompiler.java | 65 +++ .../viatra/internal/matcher/FunctionalCursor.java | 48 ++ .../internal/matcher/FunctionalViatraMatcher.java | 91 +++ .../viatra/internal/matcher/IndexerUtils.java | 48 ++ .../viatra/internal/matcher/MatcherUtils.java | 50 ++ .../matcher/OmitOutputViatraTupleLike.java | 23 + .../viatra/internal/matcher/RawPatternMatcher.java | 15 + .../viatra/internal/matcher/RelationalCursor.java | 42 ++ .../internal/matcher/RelationalViatraMatcher.java | 89 +++ .../internal/matcher/UnsafeFunctionalCursor.java | 52 ++ .../viatra/internal/matcher/ViatraTupleLike.java | 23 + .../internal/pquery/AssumptionEvaluator.java | 16 + .../query/viatra/internal/pquery/Dnf2PQuery.java | 201 ++++--- .../query/viatra/internal/pquery/IndexerUtils.java | 48 -- .../internal/pquery/QueryWrapperFactory.java | 173 ++++++ .../query/viatra/internal/pquery/RawPQuery.java | 1 + .../viatra/internal/pquery/RawPatternMatcher.java | 93 ---- .../internal/pquery/RelationViewWrapper.java | 5 + .../viatra/internal/pquery/ResultSetCursor.java | 43 -- .../pquery/StatefulMultisetAggregator.java | 60 ++ .../pquery/StatelessMultisetAggregator.java | 50 ++ .../viatra/internal/pquery/TermEvaluator.java | 32 ++ .../pquery/ValueProviderBasedValuation.java | 14 + .../internal/update/ModelUpdateListener.java | 3 +- .../update/RelationViewUpdateListener.java | 22 +- .../TupleChangingRelationViewUpdateListener.java | 6 +- .../TuplePreservingRelationViewUpdateListener.java | 5 +- .../store/query/viatra/DiagonalQueryTest.java | 471 ++++++++++++++++ .../store/query/viatra/FunctionalQueryTest.java | 607 +++++++++++++++++++++ .../refinery/store/query/viatra/QueryTest.java | 428 +++++++++------ .../store/query/viatra/QueryTransactionTest.java | 384 ++++++++++++- .../store/query/viatra/tests/QueryAssertions.java | 52 ++ .../store/query/viatra/tests/QueryBackendHint.java | 22 + .../store/query/viatra/tests/QueryEngineTest.java | 16 + .../viatra/tests/QueryEvaluationHintSource.java | 19 + .../tools/refinery/store/query/AnyResultSet.java | 11 + .../tools/refinery/store/query/Constraint.java | 65 +++ .../main/java/tools/refinery/store/query/Dnf.java | 175 ------ .../tools/refinery/store/query/DnfBuilder.java | 115 ---- .../java/tools/refinery/store/query/DnfClause.java | 22 - .../java/tools/refinery/store/query/DnfUtils.java | 19 - .../tools/refinery/store/query/EmptyResultSet.java | 22 +- .../refinery/store/query/FunctionalDependency.java | 15 - .../refinery/store/query/ModelQueryAdapter.java | 8 +- .../refinery/store/query/ModelQueryBuilder.java | 8 +- .../store/query/ModelQueryStoreAdapter.java | 5 +- .../tools/refinery/store/query/RelationLike.java | 11 - .../java/tools/refinery/store/query/ResultSet.java | 13 +- .../java/tools/refinery/store/query/Variable.java | 63 --- .../tools/refinery/store/query/dnf/AnyQuery.java | 11 + .../java/tools/refinery/store/query/dnf/Dnf.java | 194 +++++++ .../tools/refinery/store/query/dnf/DnfBuilder.java | 110 ++++ .../tools/refinery/store/query/dnf/DnfClause.java | 23 + .../tools/refinery/store/query/dnf/DnfUtils.java | 19 + .../store/query/dnf/FunctionalDependency.java | 15 + .../refinery/store/query/dnf/FunctionalQuery.java | 103 ++++ .../store/query/dnf/FunctionalQueryBuilder.java | 46 ++ .../java/tools/refinery/store/query/dnf/Query.java | 16 + .../refinery/store/query/dnf/QueryBuilder.java | 71 +++ .../refinery/store/query/dnf/RelationalQuery.java | 93 ++++ .../query/equality/DeepDnfEqualityChecker.java | 2 +- .../store/query/equality/DnfEqualityChecker.java | 2 +- .../query/equality/LiteralEqualityHelper.java | 4 +- .../store/query/literal/AbstractCallLiteral.java | 80 +++ .../store/query/literal/AggregationLiteral.java | 113 ++++ .../store/query/literal/AssignLiteral.java | 44 ++ .../store/query/literal/AssumeLiteral.java | 53 ++ .../store/query/literal/BooleanLiteral.java | 8 +- .../refinery/store/query/literal/CallLiteral.java | 105 ++-- .../refinery/store/query/literal/CanNegate.java | 5 + .../store/query/literal/ConstantLiteral.java | 11 +- .../refinery/store/query/literal/CountLiteral.java | 83 +++ .../store/query/literal/DnfCallLiteral.java | 40 -- .../store/query/literal/EquivalenceLiteral.java | 17 +- .../refinery/store/query/literal/Literal.java | 4 +- .../refinery/store/query/literal/Literals.java | 8 +- .../refinery/store/query/literal/PolarLiteral.java | 5 - .../store/query/literal/RelationViewLiteral.java | 35 -- .../query/substitution/CompositeSubstitution.java | 10 + .../query/substitution/MapBasedSubstitution.java | 2 +- .../query/substitution/RenewingSubstitution.java | 8 +- .../query/substitution/StatelessSubstitution.java | 2 +- .../store/query/substitution/Substitution.java | 23 +- .../store/query/substitution/Substitutions.java | 8 +- .../refinery/store/query/term/Aggregator.java | 13 + .../refinery/store/query/term/AnyDataVariable.java | 33 ++ .../tools/refinery/store/query/term/AnyTerm.java | 16 + .../store/query/term/ArithmeticBinaryOperator.java | 26 + .../store/query/term/ArithmeticBinaryTerm.java | 56 ++ .../store/query/term/ArithmeticUnaryOperator.java | 16 + .../store/query/term/ArithmeticUnaryTerm.java | 51 ++ .../refinery/store/query/term/AssignedValue.java | 8 + .../refinery/store/query/term/BinaryTerm.java | 93 ++++ .../store/query/term/ComparisonOperator.java | 20 + .../refinery/store/query/term/ComparisonTerm.java | 63 +++ .../refinery/store/query/term/ConstantTerm.java | 52 ++ .../tools/refinery/store/query/term/DataSort.java | 29 + .../refinery/store/query/term/DataVariable.java | 87 +++ .../store/query/term/ExtremeValueAggregator.java | 103 ++++ .../tools/refinery/store/query/term/NodeSort.java | 30 + .../refinery/store/query/term/NodeVariable.java | 48 ++ .../refinery/store/query/term/OpaqueTerm.java | 80 +++ .../java/tools/refinery/store/query/term/Sort.java | 11 + .../store/query/term/StatefulAggregate.java | 17 + .../store/query/term/StatefulAggregator.java | 23 + .../store/query/term/StatelessAggregator.java | 20 + .../java/tools/refinery/store/query/term/Term.java | 21 + .../tools/refinery/store/query/term/UnaryTerm.java | 68 +++ .../tools/refinery/store/query/term/Variable.java | 76 +++ .../store/query/term/bool/BoolConstantTerm.java | 16 + .../store/query/term/bool/BoolLogicBinaryTerm.java | 78 +++ .../store/query/term/bool/BoolNotTerm.java | 36 ++ .../refinery/store/query/term/bool/BoolTerms.java | 30 + .../store/query/term/bool/LogicBinaryOperator.java | 17 + .../query/term/int_/IntArithmeticBinaryTerm.java | 48 ++ .../query/term/int_/IntArithmeticUnaryTerm.java | 30 + .../store/query/term/int_/IntComparisonTerm.java | 34 ++ .../query/term/int_/IntExtremeValueAggregator.java | 17 + .../store/query/term/int_/IntSumAggregator.java | 35 ++ .../refinery/store/query/term/int_/IntTerms.java | 81 +++ .../store/query/term/int_/RealToIntTerm.java | 36 ++ .../store/query/term/real/IntToRealTerm.java | 36 ++ .../query/term/real/RealArithmeticBinaryTerm.java | 36 ++ .../query/term/real/RealArithmeticUnaryTerm.java | 30 + .../store/query/term/real/RealComparisonTerm.java | 35 ++ .../term/real/RealExtremeValueAggregator.java | 17 + .../store/query/term/real/RealSumAggregator.java | 85 +++ .../refinery/store/query/term/real/RealTerms.java | 81 +++ .../store/query/valuation/RestrictedValuation.java | 16 + .../query/valuation/SubstitutedValuation.java | 11 + .../refinery/store/query/valuation/Valuation.java | 23 + .../refinery/store/query/view/AnyRelationView.java | 6 +- .../store/query/view/ForbiddenRelationView.java | 16 + .../store/query/view/FunctionalRelationView.java | 38 +- .../refinery/store/query/view/MayRelationView.java | 16 + .../store/query/view/MustRelationView.java | 16 + .../refinery/store/query/view/RelationView.java | 22 +- .../query/view/TuplePreservingRelationView.java | 13 + .../tools/refinery/store/query/DnfBuilderTest.java | 50 +- .../store/query/DnfToDefinitionStringTest.java | 174 ++++++ .../refinery/store/query/DnfToStringTest.java | 172 ------ .../store/query/tests/StructurallyEqualToTest.java | 22 +- .../MismatchDescribingDnfEqualityChecker.java | 8 +- .../refinery/store/query/tests/QueryMatchers.java | 2 +- .../store/query/tests/StructurallyEqualTo.java | 4 +- .../store/reasoning/PartialInterpretation.java | 2 - .../refinery/store/reasoning/ReasoningAdapter.java | 2 +- .../refinery/store/reasoning/ReasoningBuilder.java | 2 +- .../store/reasoning/ReasoningStoreAdapter.java | 2 +- .../reasoning/internal/ReasoningAdapterImpl.java | 2 +- .../reasoning/internal/ReasoningBuilderImpl.java | 2 +- .../internal/ReasoningStoreAdapterImpl.java | 2 +- .../store/reasoning/lifting/DnfLifter.java | 71 +-- .../refinery/store/reasoning/lifting/ModalDnf.java | 4 +- .../store/reasoning/literal/ModalConstraint.java | 46 ++ .../reasoning/literal/ModalDnfCallLiteral.java | 48 -- .../store/reasoning/literal/ModalLiteral.java | 63 --- .../reasoning/literal/ModalRelationLiteral.java | 37 -- .../store/reasoning/literal/PartialLiterals.java | 32 +- .../reasoning/literal/PartialRelationLiteral.java | 32 -- .../reasoning/representation/PartialRelation.java | 36 +- .../reasoning/rule/RelationRefinementAction.java | 2 +- .../tools/refinery/store/reasoning/rule/Rule.java | 2 +- .../refinery/store/reasoning/rule/RuleAction.java | 2 +- .../tools/refinery/store/reasoning/seed/Seed.java | 4 +- .../refinery/store/reasoning/seed/UniformSeed.java | 22 + .../store/reasoning/translator/Advice.java | 2 +- .../reasoning/translator/TranslatedRelation.java | 22 + .../reasoning/translator/TranslationUnit.java | 28 +- .../base/BaseDecisionInterpretation.java | 88 +++ .../base/BaseDecisionTranslationUnit.java | 49 ++ .../translator/base/TranslatedBaseDecision.java | 49 ++ .../TypeHierarchyTranslationUnit.java | 34 +- .../java/tools/refinery/store/tuple/TupleLike.java | 10 + .../java/tools/refinery/store/tuple/TupleN.java | 4 +- 188 files changed, 7325 insertions(+), 1715 deletions(-) delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/OmitOutputViatraTupleLike.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/ViatraTupleLike.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ResultSetCursor.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java create mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java create mode 100644 subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java delete mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java create mode 100644 subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java create mode 100644 subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java delete mode 100644 subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToStringTest.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java delete mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalDnfCallLiteral.java delete mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalLiteral.java delete mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalRelationLiteral.java delete mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialRelationLiteral.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java create mode 100644 subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java (limited to 'subprojects') diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java index 24aa52e2..7ae86f9f 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java @@ -4,7 +4,8 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.AnyQuery; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.ModelQueryBuilder; import java.util.Collection; @@ -23,21 +24,21 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder { ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); @Override - default ViatraModelQueryBuilder queries(Dnf... queries) { + default ViatraModelQueryBuilder queries(AnyQuery... queries) { ModelQueryBuilder.super.queries(queries); return this; } @Override - default ViatraModelQueryBuilder queries(Collection queries) { + default ViatraModelQueryBuilder queries(Collection queries) { ModelQueryBuilder.super.queries(queries); return this; } @Override - ViatraModelQueryBuilder query(Dnf query); + ViatraModelQueryBuilder query(AnyQuery query); - ViatraModelQueryBuilder query(Dnf query, QueryEvaluationHint queryEvaluationHint); + ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint); ViatraModelQueryBuilder computeHint(Function computeHint); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java deleted file mode 100644 index 46c28434..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java +++ /dev/null @@ -1,18 +0,0 @@ -package tools.refinery.store.query.viatra; - -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import tools.refinery.store.tuple.Tuple1; -import tools.refinery.store.tuple.TupleLike; - -public record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike { - @Override - public int getSize() { - return wrappedTuple.getSize(); - } - - @Override - public int get(int element) { - var wrappedValue = (Tuple1) wrappedTuple.get(element); - return wrappedValue.value0(); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java index 37700413..8be30fee 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java @@ -7,10 +7,18 @@ import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl; import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; import tools.refinery.store.model.Model; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.model.ModelListener; +import tools.refinery.store.query.AnyResultSet; import tools.refinery.store.query.EmptyResultSet; import tools.refinery.store.query.ResultSet; +import tools.refinery.store.query.dnf.AnyQuery; +import tools.refinery.store.query.dnf.FunctionalQuery; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.dnf.RelationalQuery; import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; +import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher; +import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; +import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -19,7 +27,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { +public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener { private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE; private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; @@ -28,8 +36,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { private final Model model; private final ViatraModelQueryStoreAdapterImpl storeAdapter; private final ViatraQueryEngineImpl queryEngine; - - private final Map resultSets; + private final Map resultSets; private boolean pendingChanges; static { @@ -49,9 +56,8 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { this.model = model; this.storeAdapter = storeAdapter; var scope = new RelationalScope(this); - queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope); - - + queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope, + storeAdapter.getEngineOptions()); var querySpecifications = storeAdapter.getQuerySpecifications(); GenericQueryGroup.of( @@ -60,14 +66,28 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { var vacuousQueries = storeAdapter.getVacuousQueries(); resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size()); for (var entry : querySpecifications.entrySet()) { - var matcher = queryEngine.getMatcher(entry.getValue()); - resultSets.put(entry.getKey(), matcher); + var rawPatternMatcher = queryEngine.getMatcher(entry.getValue()); + var query = entry.getKey(); + resultSets.put(query, createResultSet((Query) query, rawPatternMatcher)); } for (var vacuousQuery : vacuousQueries) { - resultSets.put(vacuousQuery, new EmptyResultSet()); + resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query) vacuousQuery)); } setUpdatePropagationDelayed(true); + model.addListener(this); + } + + private ResultSet createResultSet(Query query, RawPatternMatcher matcher) { + if (query instanceof RelationalQuery relationalQuery) { + @SuppressWarnings("unchecked") + var resultSet = (ResultSet) new RelationalViatraMatcher(this, relationalQuery, matcher); + return resultSet; + } else if (query instanceof FunctionalQuery functionalQuery) { + return new FunctionalViatraMatcher<>(this, functionalQuery, matcher); + } else { + throw new IllegalArgumentException("Unknown query: " + query); + } } private void setUpdatePropagationDelayed(boolean value) { @@ -105,12 +125,14 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { } @Override - public ResultSet getResultSet(Dnf query) { + public ResultSet getResultSet(Query query) { var resultSet = resultSets.get(query); if (resultSet == null) { throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); } - return resultSet; + @SuppressWarnings("unchecked") + var typedResultSet = (ResultSet) resultSet; + return typedResultSet; } @Override @@ -142,4 +164,9 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { } pendingChanges = false; } + + @Override + public void afterRestore() { + flushChanges(); + } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java index 13641ace..bfabf26e 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java @@ -2,33 +2,40 @@ package tools.refinery.store.query.viatra.internal; import org.eclipse.viatra.query.runtime.api.IQuerySpecification; import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions; import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; import tools.refinery.store.adapter.AbstractModelAdapterBuilder; import tools.refinery.store.model.ModelStore; import tools.refinery.store.model.ModelStoreBuilder; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.AnyQuery; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; +import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction; +import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory; +import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery; -import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; import java.util.*; import java.util.function.Function; public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { private ViatraQueryEngineOptions.Builder engineOptionsBuilder; + private QueryEvaluationHint defaultHint = new QueryEvaluationHint(Map.of( + // Use a cost function that ignores the initial (empty) model but allows higher arity input keys. + LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction() + ), (IQueryBackendFactory) null); private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery(); - private final Set vacuousQueries = new LinkedHashSet<>(); - private final Map> querySpecifications = new LinkedHashMap<>(); + private final Set vacuousQueries = new LinkedHashSet<>(); + private final Map> querySpecifications = new LinkedHashMap<>(); public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { super(storeBuilder); engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() .withDefaultBackend(ReteBackendFactory.INSTANCE) .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) - .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); + .withDefaultSearchBackend(RelationalLocalSearchBackendFactory.INSTANCE); } @Override @@ -39,7 +46,7 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp @Override public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { - engineOptionsBuilder.withDefaultHint(queryEvaluationHint); + defaultHint = defaultHint.overrideBy(queryEvaluationHint); return this; } @@ -62,15 +69,16 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp } @Override - public ViatraModelQueryBuilder query(Dnf query) { + public ViatraModelQueryBuilder query(AnyQuery query) { if (querySpecifications.containsKey(query) || vacuousQueries.contains(query)) { // Ignore duplicate queries. return this; } - var reduction = query.getReduction(); + var dnf = query.getDnf(); + var reduction = dnf.getReduction(); switch (reduction) { case NOT_REDUCIBLE -> { - var pQuery = dnf2PQuery.translate(query); + var pQuery = dnf2PQuery.translate(dnf); querySpecifications.put(query, pQuery.build()); } case ALWAYS_FALSE -> vacuousQueries.add(query); @@ -82,9 +90,9 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp } @Override - public ViatraModelQueryBuilder query(Dnf query, QueryEvaluationHint queryEvaluationHint) { + public ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint) { + hint(query.getDnf(), queryEvaluationHint); query(query); - hint(query, queryEvaluationHint); return this; } @@ -96,26 +104,35 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp @Override public ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint) { - var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); - if (pQuery == null) { - if (vacuousQueries.contains(dnf)) { - // Ignore hits for queries that will never be executed by the query engine. - return this; - } - throw new IllegalArgumentException( - "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name())); - } - pQuery.setEvaluationHints(pQuery.getEvaluationHints().overrideBy(queryEvaluationHint)); + dnf2PQuery.hint(dnf, queryEvaluationHint); return this; } @Override public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { validateSymbols(store); - return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), + dnf2PQuery.assertNoUnusedHints(); + return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getRelationViews(), Collections.unmodifiableMap(querySpecifications), Collections.unmodifiableSet(vacuousQueries)); } + private ViatraQueryEngineOptions buildEngineOptions() { + // Workaround: manually override the default backend, because {@link ViatraQueryEngineOptions.Builder} + // ignores all backend requirements except {@code SPECIFIC}. + switch (defaultHint.getQueryBackendRequirementType()) { + case SPECIFIC -> engineOptionsBuilder.withDefaultBackend(defaultHint.getQueryBackendFactory()); + case DEFAULT_CACHING -> engineOptionsBuilder.withDefaultBackend( + engineOptionsBuilder.build().getDefaultCachingBackendFactory()); + case DEFAULT_SEARCH -> engineOptionsBuilder.withDefaultBackend( + engineOptionsBuilder.build().getDefaultSearchBackendFactory()); + case UNSPECIFIED -> { + // Nothing to do, leave the default backend unchanged. + } + } + engineOptionsBuilder.withDefaultHint(defaultHint); + return engineOptionsBuilder.build(); + } + private void validateSymbols(ModelStore store) { var symbols = store.getSymbols(); for (var relationView : dnf2PQuery.getRelationViews().keySet()) { diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java index 00660d0b..04c48c43 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java @@ -5,9 +5,9 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.AnyQuery; import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; -import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; +import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.view.AnyRelationView; import java.util.*; @@ -16,20 +16,20 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd private final ModelStore store; private final ViatraQueryEngineOptions engineOptions; private final Map inputKeys; - private final Map> querySpecifications; - private final Set vacuousQueries; - private final Set allQueries; + private final Map> querySpecifications; + private final Set vacuousQueries; + private final Set allQueries; ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, Map inputKeys, - Map> querySpecifications, - Set vacuousQueries) { + Map> querySpecifications, + Set vacuousQueries) { this.store = store; this.engineOptions = engineOptions; this.inputKeys = inputKeys; this.querySpecifications = querySpecifications; this.vacuousQueries = vacuousQueries; - var mutableAllQueries = new LinkedHashSet(querySpecifications.size() + vacuousQueries.size()); + var mutableAllQueries = new LinkedHashSet(querySpecifications.size() + vacuousQueries.size()); mutableAllQueries.addAll(querySpecifications.keySet()); mutableAllQueries.addAll(vacuousQueries); this.allQueries = Collections.unmodifiableSet(mutableAllQueries); @@ -49,15 +49,15 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd } @Override - public Collection getQueries() { + public Collection getQueries() { return allQueries; } - Map> getQuerySpecifications() { + Map> getQuerySpecifications() { return querySpecifications; } - Set getVacuousQueries() { + Set getVacuousQueries() { return vacuousQueries; } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java index cba3fa08..d2c6beb4 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java @@ -3,6 +3,8 @@ package tools.refinery.store.query.viatra.internal.context; import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; +import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey; +import tools.refinery.store.query.term.DataSort; import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; import tools.refinery.store.query.view.AnyRelationView; @@ -37,6 +39,9 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { @Override public Collection getImplications(IInputKey implyingKey) { + if (implyingKey instanceof JavaTransitiveInstancesKey) { + return List.of(); + } var relationView = checkKey(implyingKey); var relationViewImplications = relationView.getImpliedRelationViews(); var inputKeyImplications = new HashSet(relationViewImplications.size()); @@ -52,11 +57,25 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext { relationViewImplication.impliedIndices())); } } + var sorts = relationView.getSorts(); + int arity = relationView.arity(); + for (int i = 0; i < arity; i++) { + var sort = sorts.get(i); + if (sort instanceof DataSort dataSort) { + var javaTransitiveInstancesKey = new JavaTransitiveInstancesKey(dataSort.type()); + var javaImplication = new InputKeyImplication(implyingKey, javaTransitiveInstancesKey, + List.of(i)); + inputKeyImplications.add(javaImplication); + } + } return inputKeyImplications; } @Override public Map, Set> getFunctionalDependencies(IInputKey key) { + if (key instanceof JavaTransitiveInstancesKey) { + return Map.of(); + } var relationView = checkKey(key); var functionalDependencies = relationView.getFunctionalDependencies(); var flattened = new HashMap, Set>(functionalDependencies.size()); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java index 01d20d3e..854817b1 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java @@ -54,7 +54,8 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public boolean isIndexed(IInputKey key, IndexingService service) { - if (key instanceof AnyRelationView relationalKey) { + if (key instanceof RelationViewWrapper wrapper) { + var relationalKey = wrapper.getWrappedKey(); return this.modelUpdateListener.containsRelationView(relationalKey); } else { return false; @@ -83,10 +84,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { - var relationViewKey = checkKey(key); - Iterable allObjects = relationViewKey.getAll(model); - Iterable filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); - Iterator iterator = filteredBySeed.iterator(); + Iterator iterator = enumerate(key, seedMask, seed).iterator(); int result = 0; while (iterator.hasNext()) { iterator.next(); @@ -102,13 +100,25 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { @Override public Iterable enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { + var filteredBySeed = enumerate(key, seedMask, seed); + return map(filteredBySeed, Tuples::flatTupleOf); + } + + @Override + public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { + var index = seedMask.getFirstOmittedIndex().orElseThrow( + () -> new IllegalArgumentException("Seed mask does not omit a value")); + var filteredBySeed = enumerate(key, seedMask, seed); + return map(filteredBySeed, array -> array[index]); + } + + private Iterable enumerate(IInputKey key, TupleMask seedMask, ITuple seed) { var relationViewKey = checkKey(key); Iterable allObjects = relationViewKey.getAll(model); - Iterable filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); - return map(filteredBySeed, Tuples::flatTupleOf); + return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); } - private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { + private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { for (int i = 0; i < seedMask.indices.length; i++) { final Object seedElement = seed.get(i); final Object tupleElement = tuple[seedMask.indices[i]]; @@ -119,11 +129,6 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext { return true; } - @Override - public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { - return enumerateTuples(key, seedMask, seed); - } - @Override public boolean containsTuple(IInputKey key, ITuple seed) { var relationViewKey = checkKey(key); diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java new file mode 100644 index 00000000..2d3dd5a7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; +import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; +import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; + +import java.util.Iterator; + +/** + * An operation that can be used to enumerate all possible values for a single position based on a constraint + * @author Zoltan Ujhelyi, Akos Horvath + * @since 2.0 + */ +abstract class ExtendOperationExecutor implements ISearchOperationExecutor { + + private Iterator it; + + /** + * Returns an iterator with the possible options from the current state + * @since 2.0 + */ + @SuppressWarnings("squid:S1452") + protected abstract Iterator getIterator(MatchingFrame frame, ISearchContext context); + /** + * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}. + * + * @return true if the update is successful or false otherwise; in case of false is returned, the next element should be taken from the iterator. + * @since 2.0 + */ + protected abstract boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context); + + /** + * Restores the frame to the state before {@link #fillInValue(Object, MatchingFrame, ISearchContext)}. Called during + * {@link #onBacktrack(MatchingFrame, ISearchContext)}. + * + * @since 2.0 + */ + protected abstract void cleanup(MatchingFrame frame, ISearchContext context); + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + it = getIterator(frame, context); + } + + @Override + public void onBacktrack(MatchingFrame frame, ISearchContext context) { + it = null; + + } + + /** + * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute} + * that handles failed unification of variables correctly. + * @param frame The matching frame to extend. + * @param context The search context. + * @return {@code true} if an extension was found, {@code false} otherwise. + */ + @Override + public boolean execute(MatchingFrame frame, ISearchContext context) { + while (it.hasNext()) { + var newValue = it.next(); + if (fillInValue(newValue, frame, context)) { + return true; + } + } + return false; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java new file mode 100644 index 00000000..aaaece80 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; +import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; +import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation; +import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; +import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends ExtendOperationExecutor { + private final VolatileModifiableMaskedTuple maskedTuple; + + public Executor() { + maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + protected Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment()); + return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator(); + } + + /** + * @since 2.0 + */ + @Override + protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) { + TupleMask mask = information.getFullFrameMask(); + // The first loop clears out the elements from a possible previous iteration + for(int i : information.getFreeParameterIndices()) { + mask.set(frame, i, null); + } + for(int i : information.getFreeParameterIndices()) { + Object oldValue = mask.getValue(frame, i); + Object valueToFill = result.get(i); + if (oldValue != null && !oldValue.equals(valueToFill)){ + // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller. + // In this case if the callee assigns different values the frame shall be dropped + return false; + } + mask.set(frame, i, valueToFill); + } + return true; + } + + @Override + protected void cleanup(MatchingFrame frame, ISearchContext context) { + TupleMask mask = information.getFullFrameMask(); + for(int i : information.getFreeParameterIndices()){ + mask.set(frame, i, null); + } + + } + + @Override + public ISearchOperation getOperation() { + return ExtendPositivePatternCall.this; + } + } + + private final CallInformation information; + + /** + * @since 1.7 + */ + public ExtendPositivePatternCall(CallInformation information) { + this.information = information; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return information.getVariablePositions(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(@SuppressWarnings("squid:S4276") Function variableMapping) { + return "extend find " + information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java new file mode 100644 index 00000000..84cab142 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java @@ -0,0 +1,30 @@ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; + +import java.util.Optional; + +public class FlatCostFunction extends StatisticsBasedConstraintCostFunction { + public FlatCostFunction() { + // No inverse navigation penalty thanks to relational storage. + super(0); + } + + @Override + public Optional projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) { + // We always start from an empty model, where every projection is of size 0. + // Therefore, projection size estimation is meaningless. + return Optional.empty(); + } + + @Override + protected double _calculateCost(TypeConstraint constraint, IConstraintEvaluationContext input) { + // Assume a flat cost for each relation. Maybe adjust in the future if we perform indexing? + return DEFAULT_COST; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java new file mode 100644 index 00000000..64653658 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; +import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; +import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation; +import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple; +import org.eclipse.viatra.query.runtime.matchers.util.Preconditions; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public class GenericTypeExtend implements IIteratingSearchOperation { + private class Executor extends ExtendOperationExecutor { + private final VolatileMaskedTuple maskedTuple; + + public Executor() { + this.maskedTuple = new VolatileMaskedTuple(callMask); + } + + @Override + protected Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + return context.getRuntimeContext().enumerateTuples(type, indexerMask, maskedTuple).iterator(); + } + + @Override + protected boolean fillInValue(Tuple newTuple, MatchingFrame frame, ISearchContext context) { + for (Integer position : unboundVariableIndices) { + frame.setValue(position, null); + } + for (int i = 0; i < positions.length; i++) { + Object newValue = newTuple.get(i); + Object oldValue = frame.getValue(positions[i]); + if (oldValue != null && !Objects.equals(oldValue, newValue)) { + // If positions tuple maps more than one values for the same element (e.g. loop), it means that + // these arguments are to unified by the caller. In this case if the callee assigns different values + // the frame shall be considered a failed match + return false; + } + frame.setValue(positions[i], newValue); + } + return true; + } + + @Override + protected void cleanup(MatchingFrame frame, ISearchContext context) { + for (Integer position : unboundVariableIndices) { + frame.setValue(position, null); + } + } + + @Override + public ISearchOperation getOperation() { + return GenericTypeExtend.this; + } + } + + private final IInputKey type; + private final int[] positions; + private final List positionList; + private final Set unboundVariableIndices; + private final TupleMask indexerMask; + private final TupleMask callMask; + + /** + * + * @param type + * the type to execute the extend operation on + * @param positions + * the parameter positions that represent the variables of the input key + * @param unboundVariableIndices + * the set of positions that are bound at the start of the operation + */ + public GenericTypeExtend(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, Set unboundVariableIndices) { + Preconditions.checkArgument(positions.length == type.getArity(), + "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(), + type.getArity(), positions.length); + List modifiablePositionList = new ArrayList<>(); + for (int position : positions) { + modifiablePositionList.add(position); + } + this.positionList = Collections.unmodifiableList(modifiablePositionList); + this.positions = positions; + this.type = type; + + this.unboundVariableIndices = unboundVariableIndices; + this.indexerMask = indexerMask; + this.callMask = callMask; + } + + @Override + public IInputKey getIteratedInputKey() { + return type; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return positionList; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(@SuppressWarnings("squid:S4276") Function variableMapping) { + return "extend " + type.getPrettyPrintableName() + "(" + + positionList.stream() + .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input))) + .collect(Collectors.joining(", ")) + + ")"; + } + +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java new file mode 100644 index 00000000..156eb313 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java @@ -0,0 +1,55 @@ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; +import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; +import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider; +import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; + +public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory { + public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory(); + + private RelationalLocalSearchBackendFactory() { + } + + @Override + public IQueryBackend create(IQueryBackendContext context) { + return new LocalSearchBackend(context) { + // Create a new {@link IPlanProvider}, because the original {@link LocalSearchBackend#planProvider} is not + // accessible. + private final IPlanProvider planProvider = new SimplePlanProvider(context.getLogger()); + + @Override + protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, + QueryEvaluationHint hints) { + return new RelationalLocalSearchResultProvider(this, context, query, planProvider, hints); + } + + @Override + public IQueryBackendFactory getFactory() { + return RelationalLocalSearchBackendFactory.this; + } + }; + } + + @Override + public Class getBackendClass() { + return LocalSearchBackend.class; + } + + @Override + public IMatcherCapability calculateRequiredCapability(PQuery pQuery, QueryEvaluationHint queryEvaluationHint) { + return LocalSearchHints.parse(queryEvaluationHint); + } + + @Override + public boolean isCaching() { + return false; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java new file mode 100644 index 00000000..be2a38ca --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; +import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; +import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; +import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; + +class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider { + public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, + IPlanProvider planProvider, QueryEvaluationHint userHints) { + super(backend, context, query, planProvider, userHints); + } + + @Override + protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, + LocalSearchHints configuration) { + return new RelationalOperationCompiler(runtimeContext); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java new file mode 100644 index 00000000..b6832b39 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java @@ -0,0 +1,65 @@ +package tools.refinery.store.query.viatra.internal.localsearch; + +import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue; +import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; +import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler; +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; +import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; + +import java.util.*; + +public class RelationalOperationCompiler extends GenericOperationCompiler { + public RelationalOperationCompiler(IQueryRuntimeContext runtimeContext) { + super(runtimeContext); + } + + @Override + protected void createExtend(TypeConstraint typeConstraint, Map variableMapping) { + IInputKey inputKey = typeConstraint.getSupplierKey(); + Tuple tuple = typeConstraint.getVariablesTuple(); + + int[] positions = new int[tuple.getSize()]; + List boundVariableIndices = new ArrayList<>(); + List boundVariables = new ArrayList<>(); + Set unboundVariables = new HashSet<>(); + for (int i = 0; i < tuple.getSize(); i++) { + PVariable variable = (PVariable) tuple.get(i); + Integer position = variableMapping.get(variable); + positions[i] = position; + if (variableBindings.get(typeConstraint).contains(position)) { + boundVariableIndices.add(i); + boundVariables.add(position); + } else { + unboundVariables.add(position); + } + } + TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices); + TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables); + // If multiple tuple elements from the indexer should be bound to the same variable, we must use a + // {@link GenericTypeExtend} check whether the tuple elements have the same value. + if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) { + operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask, + unboundVariables.iterator().next())); + } else { + // Use a fixed version of + // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles + // failed unification of variables correctly. + operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables)); + } + } + + @Override + protected void createExtend(PositivePatternCall pCall, Map variableMapping) { + CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall)); + // Use a fixed version of + // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles + // failed unification of variables correctly. + operations.add(new ExtendPositivePatternCall(information)); + dependencies.add(information.getCallWithAdornment()); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java new file mode 100644 index 00000000..4daa14a1 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java @@ -0,0 +1,48 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Iterator; + +class FunctionalCursor implements Cursor { + private final IterableIndexer indexer; + private final Iterator iterator; + private boolean terminated; + private TupleLike key; + private T value; + + public FunctionalCursor(IterableIndexer indexer) { + this.indexer = indexer; + iterator = indexer.getSignatures().iterator(); + } + + @Override + public TupleLike getKey() { + return key; + } + + @Override + public T getValue() { + return value; + } + + @Override + public boolean isTerminated() { + return terminated; + } + + @Override + public boolean move() { + if (!terminated && iterator.hasNext()) { + var match = iterator.next(); + key = new ViatraTupleLike(match); + value = MatcherUtils.getSingleValue(indexer.get(match)); + return true; + } + terminated = true; + return false; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java new file mode 100644 index 00000000..6aa45af2 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java @@ -0,0 +1,91 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; +import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.ResultSet; +import tools.refinery.store.query.dnf.FunctionalQuery; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; +import tools.refinery.store.tuple.TupleLike; + +/** + * Directly access the tuples inside a VIATRA pattern matcher.

+ * This class neglects calling + * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} + * and + * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, + * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial + * implementation for these methods. + * Using this class with any other runtime context may lead to undefined behavior. + */ +public class FunctionalViatraMatcher implements ResultSet { + private final ViatraModelQueryAdapterImpl adapter; + private final FunctionalQuery query; + private final TupleMask emptyMask; + private final TupleMask omitOutputMask; + private final IQueryResultProvider backend; + private final IterableIndexer omitOutputIndexer; + + public FunctionalViatraMatcher(ViatraModelQueryAdapterImpl adapter, FunctionalQuery query, + RawPatternMatcher rawPatternMatcher) { + this.adapter = adapter; + this.query = query; + int arity = query.arity(); + int arityWithOutput = arity + 1; + emptyMask = TupleMask.empty(arityWithOutput); + omitOutputMask = TupleMask.omit(arity, arityWithOutput); + backend = rawPatternMatcher.getBackend(); + if (backend instanceof RetePatternMatcher reteBackend) { + var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask); + if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) { + omitOutputIndexer = iterableOmitOutputIndexer; + } else { + omitOutputIndexer = null; + } + } else { + omitOutputIndexer = null; + } + } + + @Override + public ModelQueryAdapter getAdapter() { + return adapter; + } + + @Override + public Query getQuery() { + return query; + } + + @Override + public T get(TupleLike parameters) { + var tuple = MatcherUtils.toViatraTuple(parameters); + if (omitOutputIndexer == null) { + return MatcherUtils.getSingleValue(backend.getAllMatches(omitOutputMask, tuple).iterator()); + } else { + return MatcherUtils.getSingleValue(omitOutputIndexer.get(tuple)); + } + } + + @Override + public Cursor getAll() { + if (omitOutputIndexer == null) { + var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf()); + return new UnsafeFunctionalCursor<>(allMatches.iterator()); + } + return new FunctionalCursor<>(omitOutputIndexer); + } + + @Override + public int size() { + if (omitOutputIndexer == null) { + return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf()); + } + return omitOutputIndexer.getBucketCount(); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java new file mode 100644 index 00000000..55eb8c44 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java @@ -0,0 +1,48 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.rete.index.Indexer; +import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; +import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; +import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +final class IndexerUtils { + private static final MethodHandle GET_ENGINE_HANDLE; + private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE; + private static final MethodHandle ACCESS_PROJECTION_HANDLE; + + static { + try { + var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup()); + GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class); + GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace", + RecipeTraceInfo.class); + ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection", + MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class)); + } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { + throw new IllegalStateException("Cannot access private members of %s" + .formatted(RetePatternMatcher.class.getPackageName()), e); + } + } + + private IndexerUtils() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) { + try { + var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend); + var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend); + return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask); + } catch (Error e) { + // Fatal JVM errors should not be wrapped. + throw e; + } catch (Throwable e) { + throw new IllegalStateException("Cannot access matcher for mask " + mask, e); + } + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java new file mode 100644 index 00000000..5d4be95d --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java @@ -0,0 +1,50 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.tuple.Tuple; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Iterator; + +final class MatcherUtils { + private MatcherUtils() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static org.eclipse.viatra.query.runtime.matchers.tuple.Tuple toViatraTuple(TupleLike tuple) { + if (tuple instanceof ViatraTupleLike viatraTupleLike) { + return viatraTupleLike.wrappedTuple().toImmutable(); + } + int size = tuple.getSize(); + var array = new Object[size]; + for (int i = 0; i < size; i++) { + var value = tuple.get(i); + array[i] = Tuple.of(value); + } + return Tuples.flatTupleOf(array); + } + + + public static T getSingleValue(@Nullable Iterable tuples) { + if (tuples == null) { + return null; + } + return getSingleValue(tuples.iterator()); + } + + public static T getSingleValue(Iterator iterator) { + if (!iterator.hasNext()) { + return null; + } + var match = iterator.next(); + @SuppressWarnings("unchecked") + var result = (T) match.get(match.getSize() - 1); + if (iterator.hasNext()) { + var input = new OmitOutputViatraTupleLike(match); + throw new IllegalStateException("Query is not functional for input tuple: " + input); + } + return result; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/OmitOutputViatraTupleLike.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/OmitOutputViatraTupleLike.java new file mode 100644 index 00000000..bd9301ba --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/OmitOutputViatraTupleLike.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.store.tuple.Tuple1; +import tools.refinery.store.tuple.TupleLike; + +record OmitOutputViatraTupleLike(ITuple wrappedTuple) implements TupleLike { + @Override + public int getSize() { + return wrappedTuple.getSize() - 1; + } + + @Override + public int get(int element) { + var wrappedValue = (Tuple1) wrappedTuple.get(element); + return wrappedValue.value0(); + } + + @Override + public String toString() { + return TupleLike.toString(this); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java new file mode 100644 index 00000000..b3d05967 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java @@ -0,0 +1,15 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; +import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; + +public class RawPatternMatcher extends GenericPatternMatcher { + public RawPatternMatcher(GenericQuerySpecification specification) { + super(specification); + } + + IQueryResultProvider getBackend() { + return backend; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java new file mode 100644 index 00000000..c2dcc565 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java @@ -0,0 +1,42 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Iterator; + +class RelationalCursor implements Cursor { + private final Iterator tuplesIterator; + private boolean terminated; + private TupleLike key; + + public RelationalCursor(Iterator tuplesIterator) { + this.tuplesIterator = tuplesIterator; + } + + @Override + public TupleLike getKey() { + return key; + } + + @Override + public Boolean getValue() { + return true; + } + + @Override + public boolean isTerminated() { + return terminated; + } + + @Override + public boolean move() { + if (!terminated && tuplesIterator.hasNext()) { + key = new ViatraTupleLike(tuplesIterator.next()); + return true; + } + terminated = true; + return false; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java new file mode 100644 index 00000000..b9bc3f1e --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java @@ -0,0 +1,89 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; +import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import org.eclipse.viatra.query.runtime.rete.index.Indexer; +import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.map.Cursors; +import tools.refinery.store.query.ModelQueryAdapter; +import tools.refinery.store.query.ResultSet; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.dnf.RelationalQuery; +import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; +import tools.refinery.store.tuple.TupleLike; + +/** + * Directly access the tuples inside a VIATRA pattern matcher.

+ * This class neglects calling + * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} + * and + * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, + * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial + * implementation for these methods. + * Using this class with any other runtime context may lead to undefined behavior. + */ +public class RelationalViatraMatcher implements ResultSet { + private final ViatraModelQueryAdapterImpl adapter; + private final RelationalQuery query; + private final TupleMask emptyMask; + private final TupleMask identityMask; + private final IQueryResultProvider backend; + private final Indexer emptyMaskIndexer; + + public RelationalViatraMatcher(ViatraModelQueryAdapterImpl adapter, RelationalQuery query, + RawPatternMatcher rawPatternMatcher) { + this.adapter = adapter; + this.query = query; + int arity = query.arity(); + emptyMask = TupleMask.empty(arity); + identityMask = TupleMask.identity(arity); + backend = rawPatternMatcher.getBackend(); + if (backend instanceof RetePatternMatcher reteBackend) { + emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask); + } else { + emptyMaskIndexer = null; + } + } + + @Override + public ModelQueryAdapter getAdapter() { + return adapter; + } + + @Override + public Query getQuery() { + return query; + } + + @Override + public Boolean get(TupleLike parameters) { + var tuple = MatcherUtils.toViatraTuple(parameters); + if (emptyMaskIndexer == null) { + return backend.hasMatch(identityMask, tuple); + } + var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); + return matches != null && matches.contains(tuple); + } + + @Override + public Cursor getAll() { + if (emptyMaskIndexer == null) { + var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf()); + return new RelationalCursor(allMatches.iterator()); + } + var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); + return matches == null ? Cursors.empty() : new RelationalCursor(matches.stream().iterator()); + } + + @Override + public int size() { + if (emptyMaskIndexer == null) { + return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf()); + } + var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); + return matches == null ? 0 : matches.size(); + } + +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java new file mode 100644 index 00000000..6c53fff1 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java @@ -0,0 +1,52 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.tuple.TupleLike; + +import java.util.Iterator; + +/** + * Cursor for a functional result set that iterates over a stream of raw matches and doesn't check whether the + * functional dependency of the output on the inputs is obeyed. + * @param The output type. + */ +class UnsafeFunctionalCursor implements Cursor { + private final Iterator tuplesIterator; + private boolean terminated; + private TupleLike key; + private T value; + + public UnsafeFunctionalCursor(Iterator tuplesIterator) { + this.tuplesIterator = tuplesIterator; + } + + @Override + public TupleLike getKey() { + return key; + } + + @Override + public T getValue() { + return value; + } + + @Override + public boolean isTerminated() { + return terminated; + } + + @Override + public boolean move() { + if (!terminated && tuplesIterator.hasNext()) { + var match = tuplesIterator.next(); + key = new OmitOutputViatraTupleLike(match); + @SuppressWarnings("unchecked") + var typedValue = (T) match.get(match.getSize() - 1); + value = typedValue; + return true; + } + terminated = true; + return false; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/ViatraTupleLike.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/ViatraTupleLike.java new file mode 100644 index 00000000..76a3e40b --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/ViatraTupleLike.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.viatra.internal.matcher; + +import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.store.tuple.Tuple1; +import tools.refinery.store.tuple.TupleLike; + +record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike { + @Override + public int getSize() { + return wrappedTuple.getSize(); + } + + @Override + public int get(int element) { + var wrappedValue = (Tuple1) wrappedTuple.get(element); + return wrappedValue.value0(); + } + + @Override + public String toString() { + return TupleLike.toString(this); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java new file mode 100644 index 00000000..a80b0f90 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.store.query.term.Term; + +class AssumptionEvaluator extends TermEvaluator { + public AssumptionEvaluator(Term term) { + super(term); + } + + @Override + public Object evaluateExpression(IValueProvider provider) { + var result = super.evaluateExpression(provider); + return result == null ? Boolean.FALSE : result; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java index 201e0ed0..7afeb977 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java @@ -1,45 +1,44 @@ package tools.refinery.store.query.viatra.internal.pquery; +import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; +import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; +import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*; import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue; import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.DnfClause; -import tools.refinery.store.query.DnfUtils; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.DnfClause; import tools.refinery.store.query.literal.*; +import tools.refinery.store.query.term.ConstantTerm; +import tools.refinery.store.query.term.StatefulAggregator; +import tools.refinery.store.query.term.StatelessAggregator; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.view.AnyRelationView; import tools.refinery.store.util.CycleDetectingMapper; import java.util.*; import java.util.function.Function; +import java.util.stream.Collectors; public class Dnf2PQuery { private static final Object P_CONSTRAINT_LOCK = new Object(); - private final CycleDetectingMapper mapper = new CycleDetectingMapper<>(Dnf::name, this::doTranslate); - - private final Map view2WrapperMap = new LinkedHashMap<>(); - - private final Map view2EmbeddedMap = new HashMap<>(); - + private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this); + private final Map hintOverrides = new LinkedHashMap<>(); private Function computeHint = dnf -> new QueryEvaluationHint(null, - QueryEvaluationHint.BackendRequirement.UNSPECIFIED); + (IQueryBackendFactory) null); public void setComputeHint(Function computeHint) { this.computeHint = computeHint; @@ -50,16 +49,33 @@ public class Dnf2PQuery { } public Map getRelationViews() { - return Collections.unmodifiableMap(view2WrapperMap); + return wrapperFactory.getRelationViews(); } - public RawPQuery getAlreadyTranslated(Dnf dnfQuery) { - return mapper.getAlreadyMapped(dnfQuery); + public void hint(Dnf dnf, QueryEvaluationHint hint) { + hintOverrides.compute(dnf, (ignoredKey, existingHint) -> + existingHint == null ? hint : existingHint.overrideBy(hint)); + } + + private QueryEvaluationHint consumeHint(Dnf dnf) { + var defaultHint = computeHint.apply(dnf); + var existingHint = hintOverrides.remove(dnf); + return defaultHint.overrideBy(existingHint); + } + + public void assertNoUnusedHints() { + if (hintOverrides.isEmpty()) { + return; + } + var unusedHints = hintOverrides.keySet().stream().map(Dnf::name).collect(Collectors.joining(", ")); + throw new IllegalStateException( + "Unused query evaluation hints for %s. Hints must be set before a query is added to the engine" + .formatted(unusedHints)); } private RawPQuery doTranslate(Dnf dnfQuery) { var pQuery = new RawPQuery(dnfQuery.getUniqueName()); - pQuery.setEvaluationHints(computeHint.apply(dnfQuery)); + pQuery.setEvaluationHints(consumeHint(dnfQuery)); Map parameters = new HashMap<>(); for (Variable variable : dnfQuery.getParameters()) { @@ -97,7 +113,7 @@ public class Dnf2PQuery { body.setSymbolicParameters(symbolicParameters); pQuery.addBody(body); for (Literal literal : clause.literals()) { - translateLiteral(literal, body); + translateLiteral(literal, clause, body); } } } @@ -105,15 +121,21 @@ public class Dnf2PQuery { return pQuery; } - private void translateLiteral(Literal literal, PBody body) { + private void translateLiteral(Literal literal, DnfClause clause, PBody body) { if (literal instanceof EquivalenceLiteral equivalenceLiteral) { translateEquivalenceLiteral(equivalenceLiteral, body); - } else if (literal instanceof RelationViewLiteral relationViewLiteral) { - translateRelationViewLiteral(relationViewLiteral, body); - } else if (literal instanceof DnfCallLiteral dnfCallLiteral) { - translateDnfCallLiteral(dnfCallLiteral, body); + } else if (literal instanceof CallLiteral callLiteral) { + translateCallLiteral(callLiteral, clause, body); } else if (literal instanceof ConstantLiteral constantLiteral) { translateConstantLiteral(constantLiteral, body); + } else if (literal instanceof AssignLiteral assignLiteral) { + translateAssignLiteral(assignLiteral, body); + } else if (literal instanceof AssumeLiteral assumeLiteral) { + translateAssumeLiteral(assumeLiteral, body); + } else if (literal instanceof CountLiteral countLiteral) { + translateCountLiteral(countLiteral, clause, body); + } else if (literal instanceof AggregationLiteral aggregationLiteral) { + translateAggregationLiteral(aggregationLiteral, clause, body); } else { throw new IllegalArgumentException("Unknown literal: " + literal.toString()); } @@ -129,20 +151,43 @@ public class Dnf2PQuery { } } - private void translateRelationViewLiteral(RelationViewLiteral relationViewLiteral, PBody body) { - var substitution = translateSubstitution(relationViewLiteral.getArguments(), body); - var polarity = relationViewLiteral.getPolarity(); - var relationView = relationViewLiteral.getTarget(); - if (polarity == CallPolarity.POSITIVE) { - new TypeConstraint(body, substitution, wrapView(relationView)); - } else { - var embeddedPQuery = translateEmbeddedRelationViewPQuery(relationView); - switch (polarity) { - case TRANSITIVE -> new BinaryTransitiveClosure(body, substitution, embeddedPQuery); - case NEGATIVE -> new NegativePatternCall(body, substitution, embeddedPQuery); - default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); + private void translateCallLiteral(CallLiteral callLiteral, DnfClause clause, PBody body) { + var polarity = callLiteral.getPolarity(); + switch (polarity) { + case POSITIVE -> { + var substitution = translateSubstitution(callLiteral.getArguments(), body); + var constraint = callLiteral.getTarget(); + if (constraint instanceof Dnf dnf) { + var pattern = translate(dnf); + new PositivePatternCall(body, substitution, pattern); + } else if (constraint instanceof AnyRelationView relationView) { + var inputKey = wrapperFactory.getInputKey(relationView); + new TypeConstraint(body, substitution, inputKey); + } else { + throw new IllegalArgumentException("Unknown Constraint: " + constraint); } } + case TRANSITIVE -> { + var substitution = translateSubstitution(callLiteral.getArguments(), body); + var constraint = callLiteral.getTarget(); + PQuery pattern; + if (constraint instanceof Dnf dnf) { + pattern = translate(dnf); + } else if (constraint instanceof AnyRelationView relationView) { + pattern = wrapperFactory.wrapRelationViewIdentityArguments(relationView); + } else { + throw new IllegalArgumentException("Unknown Constraint: " + constraint); + } + new BinaryTransitiveClosure(body, substitution, pattern); + } + case NEGATIVE -> { + var wrappedCall = wrapperFactory.maybeWrapConstraint(callLiteral, clause); + var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); + var pattern = wrappedCall.pattern(); + new NegativePatternCall(body, substitution, pattern); + } + default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); + } } private static Tuple translateSubstitution(List substitution, PBody body) { @@ -155,51 +200,57 @@ public class Dnf2PQuery { return Tuples.flatTupleOf(variables); } - private RawPQuery translateEmbeddedRelationViewPQuery(AnyRelationView relationView) { - return view2EmbeddedMap.computeIfAbsent(relationView, this::doTranslateEmbeddedRelationViewPQuery); + private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) { + var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName()); + new ConstantValue(body, variable, constantLiteral.nodeId()); } - private RawPQuery doTranslateEmbeddedRelationViewPQuery(AnyRelationView relationView) { - var embeddedPQuery = new RawPQuery(DnfUtils.generateUniqueName(relationView.name()), PVisibility.EMBEDDED); - var body = new PBody(embeddedPQuery); - int arity = relationView.arity(); - var parameters = new ArrayList(arity); - var arguments = new Object[arity]; - var symbolicParameters = new ArrayList(arity); - for (int i = 0; i < arity; i++) { - var parameterName = "p" + i; - var parameter = new PParameter(parameterName); - parameters.add(parameter); - var variable = body.getOrCreateVariableByName(parameterName); - arguments[i] = variable; - symbolicParameters.add(new ExportedParameter(body, variable, parameter)); + private void translateAssignLiteral(AssignLiteral assignLiteral, PBody body) { + var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName()); + var term = assignLiteral.term(); + if (term instanceof ConstantTerm constantTerm) { + new ConstantValue(body, variable, constantTerm.getValue()); + } else { + var evaluator = new TermEvaluator<>(term); + new ExpressionEvaluation(body, evaluator, variable); } - embeddedPQuery.setParameters(parameters); - body.setSymbolicParameters(symbolicParameters); - var argumentTuple = Tuples.flatTupleOf(arguments); - new TypeConstraint(body, argumentTuple, wrapView(relationView)); - embeddedPQuery.addBody(body); - return embeddedPQuery; } - private RelationViewWrapper wrapView(AnyRelationView relationView) { - return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new); + private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) { + var evaluator = new AssumptionEvaluator(assumeLiteral.term()); + new ExpressionEvaluation(body, evaluator, null); } - private void translateDnfCallLiteral(DnfCallLiteral dnfCallLiteral, PBody body) { - var variablesTuple = translateSubstitution(dnfCallLiteral.getArguments(), body); - var translatedReferred = translate(dnfCallLiteral.getTarget()); - var polarity = dnfCallLiteral.getPolarity(); - switch (polarity) { - case POSITIVE -> new PositivePatternCall(body, variablesTuple, translatedReferred); - case TRANSITIVE -> new BinaryTransitiveClosure(body, variablesTuple, translatedReferred); - case NEGATIVE -> new NegativePatternCall(body, variablesTuple, translatedReferred); - default -> throw new IllegalArgumentException("Unknown polarity: " + polarity); - } + private void translateCountLiteral(CountLiteral countLiteral, DnfClause clause, PBody body) { + var wrappedCall = wrapperFactory.maybeWrapConstraint(countLiteral, clause); + var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); + var resultVariable = body.getOrCreateVariableByName(countLiteral.getResultVariable().getUniqueName()); + new PatternMatchCounter(body, substitution, wrappedCall.pattern(), resultVariable); } - private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) { - var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName()); - new ConstantValue(body, variable, constantLiteral.nodeId()); + private void translateAggregationLiteral(AggregationLiteral aggregationLiteral, DnfClause clause, + PBody body) { + var aggregator = aggregationLiteral.getAggregator(); + IMultisetAggregationOperator aggregationOperator; + if (aggregator instanceof StatelessAggregator statelessAggregator) { + aggregationOperator = new StatelessMultisetAggregator<>(statelessAggregator); + } else if (aggregator instanceof StatefulAggregator statefulAggregator) { + aggregationOperator = new StatefulMultisetAggregator<>(statefulAggregator); + } else { + throw new IllegalArgumentException("Unknown aggregator: " + aggregator); + } + var wrappedCall = wrapperFactory.maybeWrapConstraint(aggregationLiteral, clause); + var substitution = translateSubstitution(wrappedCall.remappedArguments(), body); + var inputVariable = body.getOrCreateVariableByName(aggregationLiteral.getInputVariable().getUniqueName()); + var aggregatedColumn = substitution.invertIndex().get(inputVariable); + if (aggregatedColumn == null) { + throw new IllegalStateException("Input variable %s not found in substitution %s".formatted(inputVariable, + substitution)); + } + var boundAggregator = new BoundAggregator(aggregationOperator, aggregator.getInputType(), + aggregator.getResultType()); + var resultVariable = body.getOrCreateVariableByName(aggregationLiteral.getResultVariable().getUniqueName()); + new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable, + aggregatedColumn); } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java deleted file mode 100644 index 75588b81..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/IndexerUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -package tools.refinery.store.query.viatra.internal.pquery; - -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.rete.index.Indexer; -import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; -import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; -import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -final class IndexerUtils { - private static final MethodHandle GET_ENGINE_HANDLE; - private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE; - private static final MethodHandle ACCESS_PROJECTION_HANDLE; - - static { - try { - var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup()); - GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class); - GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace", - RecipeTraceInfo.class); - ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection", - MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class)); - } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { - throw new IllegalStateException("Cannot access private members of %s" - .formatted(RetePatternMatcher.class.getPackageName()), e); - } - } - - private IndexerUtils() { - throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); - } - - public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) { - try { - var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend); - var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend); - return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot access matcher for mask " + mask, e); - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java new file mode 100644 index 00000000..24ae5196 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java @@ -0,0 +1,173 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; +import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; +import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; +import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; +import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.DnfClause; +import tools.refinery.store.query.dnf.DnfUtils; +import tools.refinery.store.query.literal.AbstractCallLiteral; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.view.AnyRelationView; +import tools.refinery.store.query.view.RelationView; +import tools.refinery.store.util.CycleDetectingMapper; + +import java.util.*; +import java.util.function.ToIntFunction; + +class QueryWrapperFactory { + private final Dnf2PQuery dnf2PQuery; + private final Map view2WrapperMap = new LinkedHashMap<>(); + private final CycleDetectingMapper wrapConstraint = new CycleDetectingMapper<>( + RemappedConstraint::toString, this::doWrapConstraint); + + QueryWrapperFactory(Dnf2PQuery dnf2PQuery) { + this.dnf2PQuery = dnf2PQuery; + } + + public PQuery wrapRelationViewIdentityArguments(AnyRelationView relationView) { + var identity = new int[relationView.arity()]; + for (int i = 0; i < identity.length; i++) { + identity[i] = i; + } + return maybeWrapConstraint(relationView, identity); + } + public WrappedCall maybeWrapConstraint(AbstractCallLiteral callLiteral, DnfClause clause) { + var arguments = callLiteral.getArguments(); + int arity = arguments.size(); + var remappedParameters = new int[arity]; + var boundVariables = clause.boundVariables(); + var unboundVariableIndices = new HashMap(); + var appendVariable = new VariableAppender(); + for (int i = 0; i < arity; i++) { + var variable = arguments.get(i); + if (boundVariables.contains(variable)) { + // Do not join bound variable to make sure that the embedded pattern stays as general as possible. + remappedParameters[i] = appendVariable.applyAsInt(variable); + } else { + remappedParameters[i] = unboundVariableIndices.computeIfAbsent(variable, appendVariable::applyAsInt); + } + } + var pattern = maybeWrapConstraint(callLiteral.getTarget(), remappedParameters); + return new WrappedCall(pattern, appendVariable.getRemappedArguments()); + } + + private PQuery maybeWrapConstraint(Constraint constraint, int[] remappedParameters) { + if (remappedParameters.length != constraint.arity()) { + throw new IllegalArgumentException("Constraint %s expected %d parameters, but got %d parameters".formatted( + constraint, constraint.arity(), remappedParameters.length)); + } + if (constraint instanceof Dnf dnf && isIdentity(remappedParameters)) { + return dnf2PQuery.translate(dnf); + } + return wrapConstraint.map(new RemappedConstraint(constraint, remappedParameters)); + } + + private static boolean isIdentity(int[] remappedParameters) { + for (int i = 0; i < remappedParameters.length; i++) { + if (remappedParameters[i] != i) { + return false; + } + } + return true; + } + + private RawPQuery doWrapConstraint(RemappedConstraint remappedConstraint) { + var constraint = remappedConstraint.constraint(); + var remappedParameters = remappedConstraint.remappedParameters(); + + var embeddedPQuery = new RawPQuery(DnfUtils.generateUniqueName(constraint.name()), PVisibility.EMBEDDED); + var body = new PBody(embeddedPQuery); + int arity = Arrays.stream(remappedParameters).max().orElse(-1) + 1; + var parameters = new ArrayList(arity); + var parameterVariables = new PVariable[arity]; + var symbolicParameters = new ArrayList(arity); + for (int i = 0; i < arity; i++) { + var parameterName = "p" + i; + var parameter = new PParameter(parameterName); + parameters.add(parameter); + var variable = body.getOrCreateVariableByName(parameterName); + parameterVariables[i] = variable; + symbolicParameters.add(new ExportedParameter(body, variable, parameter)); + } + embeddedPQuery.setParameters(parameters); + body.setSymbolicParameters(symbolicParameters); + + var arguments = new Object[remappedParameters.length]; + for (int i = 0; i < remappedParameters.length; i++) { + arguments[i] = parameterVariables[remappedParameters[i]]; + } + var argumentTuple = Tuples.flatTupleOf(arguments); + + if (constraint instanceof RelationView relationView) { + new TypeConstraint(body, argumentTuple, getInputKey(relationView)); + } else if (constraint instanceof Dnf dnf) { + var calledPQuery = dnf2PQuery.translate(dnf); + new PositivePatternCall(body, argumentTuple, calledPQuery); + } else { + throw new IllegalArgumentException("Unknown Constraint: " + constraint); + } + + embeddedPQuery.addBody(body); + return embeddedPQuery; + } + + public IInputKey getInputKey(AnyRelationView relationView) { + return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new); + } + + public Map getRelationViews() { + return Collections.unmodifiableMap(view2WrapperMap); + } + + public record WrappedCall(PQuery pattern, List remappedArguments) { + } + + private static class VariableAppender implements ToIntFunction { + private final List remappedArguments = new ArrayList<>(); + private int nextIndex = 0; + + @Override + public int applyAsInt(Variable variable) { + remappedArguments.add(variable); + int index = nextIndex; + nextIndex++; + return index; + } + + public List getRemappedArguments() { + return remappedArguments; + } + } + + private record RemappedConstraint(Constraint constraint, int[] remappedParameters) { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RemappedConstraint that = (RemappedConstraint) o; + return constraint.equals(that.constraint) && Arrays.equals(remappedParameters, that.remappedParameters); + } + + @Override + public int hashCode() { + int result = Objects.hash(constraint); + result = 31 * result + Arrays.hashCode(remappedParameters); + return result; + } + + @Override + public String toString() { + return "RemappedConstraint{constraint=%s, remappedParameters=%s}".formatted(constraint, + Arrays.toString(remappedParameters)); + } + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java index 71b74396..aad4ba3c 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java @@ -9,6 +9,7 @@ import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; import tools.refinery.store.query.viatra.internal.RelationalScope; +import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import java.util.LinkedHashSet; import java.util.List; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java deleted file mode 100644 index 5924ff15..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java +++ /dev/null @@ -1,93 +0,0 @@ -package tools.refinery.store.query.viatra.internal.pquery; - -import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; -import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import org.eclipse.viatra.query.runtime.rete.index.Indexer; -import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.map.Cursors; -import tools.refinery.store.query.ResultSet; -import tools.refinery.store.query.viatra.ViatraTupleLike; -import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; - -/** - * Directly access the tuples inside a VIATRA pattern matcher.

- * This class neglects calling - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} - * and - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, - * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial - * implementation for these methods. - * Using this class with any other runtime context may lead to undefined behavior. - */ -public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet { - private final Object[] empty; - private final TupleMask identityMask; - private Indexer emptyMaskIndexer; - - public RawPatternMatcher(GenericQuerySpecification specification) { - super(specification); - var arity = specification.getParameterNames().size(); - empty = new Object[arity]; - identityMask = TupleMask.identity(arity); - } - - @Override - protected void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, - IMatcherCapability capabilities) { - super.setBackend(engine, resultProvider, capabilities); - if (resultProvider instanceof RetePatternMatcher reteBackend) { - emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, TupleMask.empty(identityMask.sourceWidth)); - } - } - - @Override - public boolean hasResult(TupleLike parameters) { - org.eclipse.viatra.query.runtime.matchers.tuple.Tuple tuple; - if (parameters instanceof ViatraTupleLike viatraTupleLike) { - tuple = viatraTupleLike.wrappedTuple().toImmutable(); - } else { - var parametersArray = toParametersArray(parameters); - tuple = Tuples.flatTupleOf(parametersArray); - } - if (emptyMaskIndexer == null) { - return backend.hasMatch(identityMask, tuple); - } - var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); - return matches != null && matches.contains(tuple); - } - - @Override - public Cursor allResults() { - if (emptyMaskIndexer == null) { - return new ResultSetCursor(backend.getAllMatches(empty).iterator()); - } - var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); - return matches == null ? Cursors.empty() : new ResultSetCursor(matches.stream().iterator()); - } - - @Override - public int countResults() { - if (emptyMaskIndexer == null) { - return backend.countMatches(empty); - } - var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf()); - return matches == null ? 0 : matches.size(); - } - - private Object[] toParametersArray(TupleLike tuple) { - int size = tuple.getSize(); - var array = new Object[size]; - for (int i = 0; i < size; i++) { - var value = tuple.get(i); - array[i] = Tuple.of(value); - } - return array; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java index c442add8..48bf558d 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java @@ -27,4 +27,9 @@ public class RelationViewWrapper extends BaseInputKeyWrapper { public boolean isEnumerable() { return true; } + + @Override + public String toString() { + return "RelationViewWrapper{wrappedKey=%s}".formatted(wrappedKey); + } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ResultSetCursor.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ResultSetCursor.java deleted file mode 100644 index 5e6d1970..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ResultSetCursor.java +++ /dev/null @@ -1,43 +0,0 @@ -package tools.refinery.store.query.viatra.internal.pquery; - -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import tools.refinery.store.map.Cursor; -import tools.refinery.store.query.viatra.ViatraTupleLike; -import tools.refinery.store.tuple.TupleLike; - -import java.util.Iterator; - -class ResultSetCursor implements Cursor { - private final Iterator tuplesIterator; - private boolean terminated; - private TupleLike key; - - public ResultSetCursor(Iterator tuplesIterator) { - this.tuplesIterator = tuplesIterator; - } - - @Override - public TupleLike getKey() { - return key; - } - - @Override - public Boolean getValue() { - return true; - } - - @Override - public boolean isTerminated() { - return terminated; - } - - @Override - public boolean move() { - if (!terminated && tuplesIterator.hasNext()) { - key = new ViatraTupleLike(tuplesIterator.next()); - return true; - } - terminated = true; - return false; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java new file mode 100644 index 00000000..2798a252 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java @@ -0,0 +1,60 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.store.query.term.StatefulAggregate; +import tools.refinery.store.query.term.StatefulAggregator; + +import java.util.stream.Stream; + +record StatefulMultisetAggregator(StatefulAggregator aggregator) + implements IMultisetAggregationOperator, R> { + @Override + public String getShortDescription() { + return getName(); + } + + @Override + public String getName() { + return aggregator.toString(); + } + + @Override + public StatefulAggregate createNeutral() { + return aggregator.createEmptyAggregate(); + } + + @Override + public boolean isNeutral(StatefulAggregate result) { + return result.isEmpty(); + } + + @Override + public StatefulAggregate update(StatefulAggregate oldResult, T updateValue, boolean isInsertion) { + if (isInsertion) { + oldResult.add(updateValue); + } else { + oldResult.remove(updateValue); + } + return oldResult; + } + + @Override + public R getAggregate(StatefulAggregate result) { + return result.getResult(); + } + + @Override + public R aggregateStream(Stream stream) { + return aggregator.aggregateStream(stream); + } + + @Override + public StatefulAggregate clone(StatefulAggregate original) { + return original.deepCopy(); + } + + @Override + public boolean contains(T value, StatefulAggregate accumulator) { + return accumulator.contains(value); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java new file mode 100644 index 00000000..7cc71ee9 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java @@ -0,0 +1,50 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.store.query.term.StatelessAggregator; + +import java.util.stream.Stream; + +record StatelessMultisetAggregator(StatelessAggregator aggregator) + implements IMultisetAggregationOperator { + @Override + public String getShortDescription() { + return getName(); + } + + @Override + public String getName() { + return aggregator.toString(); + } + + @Override + public R createNeutral() { + return aggregator.getEmptyResult(); + } + + @Override + public boolean isNeutral(R result) { + return createNeutral().equals(result); + } + + @Override + public R update(R oldResult, T updateValue, boolean isInsertion) { + return isInsertion ? aggregator.add(oldResult, updateValue) : aggregator.remove(oldResult, updateValue); + } + + @Override + public R getAggregate(R result) { + return result; + } + + @Override + public R clone(R original) { + // Aggregate result is immutable. + return original; + } + + @Override + public R aggregateStream(Stream stream) { + return aggregator.aggregateStream(stream); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java new file mode 100644 index 00000000..ab123c50 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java @@ -0,0 +1,32 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator; +import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.Variable; + +import java.util.stream.Collectors; + +class TermEvaluator implements IExpressionEvaluator { + private final Term term; + + public TermEvaluator(Term term) { + this.term = term; + } + + @Override + public String getShortDescription() { + return term.toString(); + } + + @Override + public Iterable getInputParameterNames() { + return term.getInputVariables().stream().map(Variable::getUniqueName).collect(Collectors.toUnmodifiableSet()); + } + + @Override + public Object evaluateExpression(IValueProvider provider) { + var valuation = new ValueProviderBasedValuation(provider); + return term.evaluate(valuation); + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java new file mode 100644 index 00000000..30c2fce7 --- /dev/null +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java @@ -0,0 +1,14 @@ +package tools.refinery.store.query.viatra.internal.pquery; + +import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.valuation.Valuation; + +public record ValueProviderBasedValuation(IValueProvider valueProvider) implements Valuation { + @Override + public T getValue(DataVariable variable) { + @SuppressWarnings("unchecked") + var value = (T) valueProvider.getValue(variable.getUniqueName()); + return value; + } +} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java index 8a467066..fc935aa6 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java @@ -22,10 +22,9 @@ public class ModelUpdateListener { } private void registerView(ViatraModelQueryAdapterImpl adapter, RelationView relationView) { - var listener = RelationViewUpdateListener.of(adapter, relationView); var model = adapter.getModel(); var interpretation = model.getInterpretation(relationView.getSymbol()); - interpretation.addListener(listener, true); + var listener = RelationViewUpdateListener.of(adapter, relationView, interpretation); relationViewUpdateListeners.put(relationView, listener); } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java index bf6b4197..5e5f60e3 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java @@ -4,6 +4,7 @@ import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.InterpretationListener; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.RelationView; @@ -14,18 +15,27 @@ import java.util.List; public abstract class RelationViewUpdateListener implements InterpretationListener { private final ViatraModelQueryAdapterImpl adapter; + private final Interpretation interpretation; private final List filters = new ArrayList<>(); - protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter) { + protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, Interpretation interpretation) { this.adapter = adapter; + this.interpretation = interpretation; } public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { + if (filters.isEmpty()) { + // First filter to be added, from now on we have to subscribe to model updates. + interpretation.addListener(this, true); + } filters.add(new RelationViewFilter(inputKey, seed, listener)); } public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { - filters.remove(new RelationViewFilter(inputKey, seed, listener)); + if (filters.remove(new RelationViewFilter(inputKey, seed, listener)) && filters.isEmpty()) { + // Last listener to be added, we don't have be subscribed to model updates anymore. + interpretation.removeListener(this); + } } protected void processUpdate(Tuple tuple, boolean isInsertion) { @@ -39,10 +49,12 @@ public abstract class RelationViewUpdateListener implements InterpretationLis } public static RelationViewUpdateListener of(ViatraModelQueryAdapterImpl adapter, - RelationView relationView) { + RelationView relationView, + Interpretation interpretation) { if (relationView instanceof TuplePreservingRelationView tuplePreservingRelationView) { - return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView); + return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView, + interpretation); } - return new TupleChangingRelationViewUpdateListener<>(adapter, relationView); + return new TupleChangingRelationViewUpdateListener<>(adapter, relationView, interpretation); } } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java index 14142884..0f6ed3b3 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java @@ -1,6 +1,7 @@ package tools.refinery.store.query.viatra.internal.update; import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.store.model.Interpretation; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.RelationView; import tools.refinery.store.tuple.Tuple; @@ -10,8 +11,9 @@ import java.util.Arrays; public class TupleChangingRelationViewUpdateListener extends RelationViewUpdateListener { private final RelationView relationView; - TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView relationView) { - super(adapter); + TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView relationView, + Interpretation interpretation) { + super(adapter, interpretation); this.relationView = relationView; } diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java index 288e018a..91e9371b 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java @@ -1,6 +1,7 @@ package tools.refinery.store.query.viatra.internal.update; import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.store.model.Interpretation; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.TuplePreservingRelationView; import tools.refinery.store.tuple.Tuple; @@ -9,8 +10,8 @@ public class TuplePreservingRelationViewUpdateListener extends RelationViewUp private final TuplePreservingRelationView view; TuplePreservingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, - TuplePreservingRelationView view) { - super(adapter); + TuplePreservingRelationView view, Interpretation interpretation) { + super(adapter, interpretation); this.view = view; } diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java new file mode 100644 index 00000000..90229b73 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java @@ -0,0 +1,471 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; +import tools.refinery.store.query.view.FunctionalRelationView; +import tools.refinery.store.query.view.KeyOnlyRelationView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.tuple.Tuple; + +import java.util.Map; +import java.util.Optional; + +import static tools.refinery.store.query.literal.Literals.not; +import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; + +class DiagonalQueryTest { + @QueryEngineTest + void inputKeyNegationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + not(symbolView.call(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void subQueryNegationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + not(subQuery.call(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyCountTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(symbolView.count(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(2), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void subQueryCountTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.count(p1, p1, p2, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(2), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyAggregationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new FunctionalRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(symbolView.aggregate(y, INT_SUM, p1, p1, p2, p2, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(3), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void subQueryAggregationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 4, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new FunctionalRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var z = Variable.of("z", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2, p3, p4, x, y) + .clause( + personView.call(p1), + symbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2, p3, p4, x), + y.assign(x) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.aggregate(z, INT_SUM, p1, p1, p2, p2, z, z)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1); + symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2); + symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10); + symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11); + symbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(3), + Tuple.of(1), Optional.of(0), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyTransitiveTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 2, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + symbolView.callTransitive(p1, p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0), true); + symbolInterpretation.put(Tuple.of(0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void subQueryTransitiveTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var symbol = new Symbol<>("symbol", 2, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var symbolView = new KeyOnlyRelationView<>(symbol); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2) + .clause( + personView.call(p1), + symbolView.call(p1, p2) + ) + .clause( + personView.call(p2), + symbolView.call(p1, p2) + ) + .build(); + var query = Query.builder("Diagonal") + .parameter(p1) + .clause( + personView.call(p1), + subQuery.callTransitive(p1, p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, symbol) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var symbolInterpretation = model.getInterpretation(symbol); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + symbolInterpretation.put(Tuple.of(0, 0), true); + symbolInterpretation.put(Tuple.of(0, 1), true); + symbolInterpretation.put(Tuple.of(1, 2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } +} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java new file mode 100644 index 00000000..fa2a008f --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java @@ -0,0 +1,607 @@ +package tools.refinery.store.query.viatra; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.store.map.Cursor; +import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.ModelQuery; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; +import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; +import tools.refinery.store.query.view.KeyOnlyRelationView; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +import java.util.Map; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.store.query.literal.Literals.assume; +import static tools.refinery.store.query.term.int_.IntTerms.*; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; + +class FunctionalQueryTest { + @QueryEngineTest + void inputKeyTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("InputKey") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + ageInterpretation.put(Tuple.of(2), 36); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(12), + Tuple.of(1), Optional.of(24), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void predicateTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + var query = Query.builder("Predicate") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + subQuery.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + ageInterpretation.put(Tuple.of(2), 36); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(12), + Tuple.of(1), Optional.of(24), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void computationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Computation") + .parameter(p1) + .output(y) + .clause( + personView.call(p1), + ageView.call(p1, x), + y.assign(mul(x, constant(7))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(84), + Tuple.of(1), Optional.of(168), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyCountTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Count") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(friendMustView.count(p1, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(1), + Tuple.of(1), Optional.of(2), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void predicateCountTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, p2) + .clause( + personView.call(p1), + personView.call(p2), + friendMustView.call(p1, p2) + ) + .build(); + var query = Query.builder("Count") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + x.assign(subQuery.count(p1, p2)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.of(1), + Tuple.of(1), Optional.of(2), + Tuple.of(2), Optional.of(0), + Tuple.of(3), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void inputKeyAggregationTest(QueryEvaluationHint hint) { + var age = new Symbol<>("age", 1, Integer.class, null); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("Aggregate") + .output(x) + .clause( + x.assign(ageView.aggregate(y, INT_SUM, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 36), queryResultSet); + } + + @QueryEngineTest + void predicateAggregationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + var query = Query.builder("Aggregate") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_SUM, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 36), queryResultSet); + } + + @QueryEngineTest + void extremeValueTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var subQuery = Dnf.builder("SubQuery") + .parameters(p1, x) + .clause( + personView.call(p1), + x.assign(friendMustView.count(p1, p2)) + ) + .build(); + var minQuery = Query.builder("Min") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_MIN, p1, y)) + ) + .build(); + var maxQuery = Query.builder("Max") + .output(x) + .clause( + x.assign(subQuery.aggregate(y, INT_MAX, p1, y)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(minQuery, maxQuery) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var minResultSet = queryEngine.getResultSet(minQuery); + var maxResultSet = queryEngine.getResultSet(maxQuery); + + assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet); + assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 0), minResultSet); + assertResults(Map.of(Tuple.of(), 2), maxResultSet); + + friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 1), minResultSet); + assertResults(Map.of(Tuple.of(), 2), maxResultSet); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE); + friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(), 0), minResultSet); + assertResults(Map.of(Tuple.of(), 1), maxResultSet); + } + + @QueryEngineTest + void invalidComputationTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var y = Variable.of("y", Integer.class); + var query = Query.builder("InvalidComputation") + .parameter(p1) + .output(y) + .clause( + personView.call(p1), + ageView.call(p1, x), + y.assign(div(constant(120), x)) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 0); + ageInterpretation.put(Tuple.of(1), 30); + + queryEngine.flushChanges(); + assertNullableResults(Map.of( + Tuple.of(0), Optional.empty(), + Tuple.of(1), Optional.of(4), + Tuple.of(2), Optional.empty() + ), queryResultSet); + } + + @QueryEngineTest + void invalidAssumeTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("InvalidComputation") + .parameter(p1) + .clause( + personView.call(p1), + ageView.call(p1, x), + assume(lessEq(div(constant(120), x), constant(5))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + ageInterpretation.put(Tuple.of(0), 0); + ageInterpretation.put(Tuple.of(1), 30); + ageInterpretation.put(Tuple.of(2), 20); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), queryResultSet); + } + + @QueryEngineTest + void notFunctionalTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); + + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("NotFunctional") + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + friendMustView.call(p1, p2), + ageView.call(p2, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age, friend) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .query(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var friendInterpretation = model.getInterpretation(friend); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + personInterpretation.put(Tuple.of(2), true); + + ageInterpretation.put(Tuple.of(0), 24); + ageInterpretation.put(Tuple.of(1), 30); + ageInterpretation.put(Tuple.of(2), 36); + + friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); + friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); + + queryEngine.flushChanges(); + var invalidTuple = Tuple.of(1); + var cursor = queryResultSet.getAll(); + assertAll( + () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)), + () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple), + "multiple values for key 1"), + () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())), + () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue())) + ); + if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) { + // Local search doesn't support throwing an error on multiple function return values. + assertThat("results size", queryResultSet.size(), is(2)); + assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor"); + } + } + + private static void enumerateValues(Cursor cursor) { + //noinspection StatementWithEmptyBody + while (cursor.move()) { + // Nothing do, just let the cursor move through the result set. + } + } +} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java index 8b25419d..8a3f9d88 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java @@ -1,33 +1,38 @@ package tools.refinery.store.query.viatra; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import org.junit.jupiter.api.Test; -import tools.refinery.store.map.Cursor; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; import tools.refinery.store.query.ModelQuery; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.viatra.tests.QueryEngineTest; import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; -import tools.refinery.store.tuple.TupleLike; -import java.util.HashSet; -import java.util.Set; +import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.refinery.store.query.literal.Literals.assume; import static tools.refinery.store.query.literal.Literals.not; +import static tools.refinery.store.query.term.int_.IntTerms.constant; +import static tools.refinery.store.query.term.int_.IntTerms.greaterEq; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; class QueryTest { - @Test - void typeConstraintTest() { + @QueryEngineTest + void typeConstraintTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var asset = new Symbol<>("Asset", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("TypeConstraint") + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") .parameters(p1) .clause(personView.call(p1)) .build(); @@ -35,7 +40,8 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, asset) .with(ViatraModelQuery.ADAPTER) - .queries(predicate) + .defaultHint(hint) + .query(predicate) .build(); var model = store.createEmptyModel(); @@ -51,20 +57,23 @@ class QueryTest { assetInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false + ), predicateResultSet); } - @Test - void relationConstraintTest() { + @QueryEngineTest + void relationConstraintTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("RelationConstraint") .parameters(p1, p2) .clause( personView.call(p1), @@ -76,6 +85,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -85,8 +95,6 @@ class QueryTest { var queryEngine = model.getAdapter(ModelQuery.ADAPTER); var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, predicateResultSet.countResults()); - personInterpretation.put(Tuple.of(0), true); personInterpretation.put(Tuple.of(1), true); personInterpretation.put(Tuple.of(2), true); @@ -94,83 +102,27 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - - assertEquals(0, predicateResultSet.countResults()); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 1))); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 2))); + friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); - assertTrue(predicateResultSet.hasResult(Tuple.of(0, 1))); - assertFalse(predicateResultSet.hasResult(Tuple.of(0, 2))); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); } - @Test - void andTest() { + @QueryEngineTest + void existTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") - .parameters(p1, p2) - .clause( - personView.call(p1), - personView.call(p2), - friendMustView.call(p1, p2), - friendMustView.call(p2, p1) - ) - .build(); - - var store = ModelStore.builder() - .symbols(person, friend) - .with(ViatraModelQuery.ADAPTER) - .queries(predicate) - .build(); - - var model = store.createEmptyModel(); - var personInterpretation = model.getInterpretation(person); - var friendInterpretation = model.getInterpretation(friend); - var queryEngine = model.getAdapter(ModelQuery.ADAPTER); - var predicateResultSet = queryEngine.getResultSet(predicate); - - assertEquals(0, predicateResultSet.countResults()); - - personInterpretation.put(Tuple.of(0), true); - personInterpretation.put(Tuple.of(1), true); - personInterpretation.put(Tuple.of(2), true); - - friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); - friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); - - queryEngine.flushChanges(); - assertEquals(0, predicateResultSet.countResults()); - - friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); - queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0))); - - friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE); - queryEngine.flushChanges(); - assertEquals(4, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2), - Tuple.of(2, 0))); - } - - @Test - void existTest() { - var person = new Symbol<>("Person", 1, Boolean.class, false); - var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); - var personView = new KeyOnlyRelationView<>(person); - var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("RelationConstraint") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("RelationConstraint") .parameters(p1) .clause( personView.call(p1), @@ -182,6 +134,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -198,16 +151,19 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); - - assertEquals(0, predicateResultSet.countResults()); + friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void orTest() { + @QueryEngineTest + void orTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var animal = new Symbol<>("Animal", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); @@ -215,9 +171,9 @@ class QueryTest { var animalView = new KeyOnlyRelationView<>(animal); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("Or") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("Or") .parameters(p1, p2) .clause( personView.call(p1), @@ -234,6 +190,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, animal, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -256,18 +213,23 @@ class QueryTest { friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(0, 2), false, + Tuple.of(2, 3), true, + Tuple.of(3, 0), false, + Tuple.of(3, 2), false + ), predicateResultSet); } - @Test - void equalityTest() { + @QueryEngineTest + void equalityTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("Equality") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("Equality") .parameters(p1, p2) .clause( personView.call(p1), @@ -279,6 +241,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -292,21 +255,26 @@ class QueryTest { personInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(1, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(3, 3), false + ), predicateResultSet); } - @Test - void inequalityTest() { + @QueryEngineTest + void inequalityTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var p3 = new Variable("p3"); - var predicate = Dnf.builder("Inequality") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var p3 = Variable.of("p3"); + var predicate = Query.builder("Inequality") .parameters(p1, p2, p3) .clause( personView.call(p1), @@ -320,6 +288,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -337,19 +306,22 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); - compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); + assertResults(Map.of( + Tuple.of(0, 1, 2), true, + Tuple.of(1, 0, 2), true, + Tuple.of(0, 0, 2), false + ), predicateResultSet); } - @Test - void patternCallTest() { + @QueryEngineTest + void patternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -359,9 +331,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("PositivePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("PositivePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -373,6 +345,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -391,19 +364,24 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 1), true, + Tuple.of(1, 0), true, + Tuple.of(1, 2), true, + Tuple.of(2, 1), false + ), predicateResultSet); } - @Test - void negativeRelationViewTest() { + @QueryEngineTest + void negativeRelationViewTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("NegativePatternCall") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("NegativePatternCall") .parameters(p1, p2) .clause( personView.call(p1), @@ -415,6 +393,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -433,18 +412,29 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(6, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 2), true, + Tuple.of(1, 1), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(1, 0), false, + Tuple.of(1, 2), false, + Tuple.of(0, 3), false + ), predicateResultSet); } - @Test - void negativePatternCallTest() { + @QueryEngineTest + void negativePatternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -454,9 +444,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("NegativePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("NegativePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -468,6 +458,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -486,20 +477,31 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(6, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 0), true, + Tuple.of(0, 2), true, + Tuple.of(1, 1), true, + Tuple.of(2, 0), true, + Tuple.of(2, 1), true, + Tuple.of(2, 2), true, + Tuple.of(0, 1), false, + Tuple.of(1, 0), false, + Tuple.of(1, 2), false, + Tuple.of(0, 3), false + ), predicateResultSet); } - @Test - void negativeRelationViewWithQuantificationTest() { + @QueryEngineTest + void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); - var predicate = Dnf.builder("Count") + var predicate = Query.builder("Count") .parameters(p1) .clause( personView.call(p1), @@ -510,6 +512,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -527,18 +530,23 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void negativeWithQuantificationTest() { + @QueryEngineTest + void negativeWithQuantificationTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var called = Dnf.builder("Called") .parameters(p1, p2) @@ -549,7 +557,7 @@ class QueryTest { ) .build(); - var predicate = Dnf.builder("Count") + var predicate = Query.builder("Count") .parameters(p1) .clause( personView.call(p1), @@ -560,6 +568,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -577,19 +586,24 @@ class QueryTest { friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); } - @Test - void transitiveRelationViewTest() { + @QueryEngineTest + void transitiveRelationViewTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); - var predicate = Dnf.builder("TransitivePatternCall") + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); + var predicate = Query.builder("TransitivePatternCall") .parameters(p1, p2) .clause( personView.call(p1), @@ -601,6 +615,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -618,18 +633,29 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 0), false, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 0), false, + Tuple.of(1, 1), false, + Tuple.of(1, 2), true, + Tuple.of(2, 0), false, + Tuple.of(2, 1), false, + Tuple.of(2, 2), false, + Tuple.of(2, 3), false + ), predicateResultSet); } - @Test - void transitivePatternCallTest() { + @QueryEngineTest + void transitivePatternCallTest(QueryEvaluationHint hint) { var person = new Symbol<>("Person", 1, Boolean.class, false); var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); var personView = new KeyOnlyRelationView<>(person); var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); - var p1 = new Variable("p1"); - var p2 = new Variable("p2"); + var p1 = Variable.of("p1"); + var p2 = Variable.of("p2"); var friendPredicate = Dnf.builder("RelationConstraint") .parameters(p1, p2) .clause( @@ -639,9 +665,9 @@ class QueryTest { ) .build(); - var p3 = new Variable("p3"); - var p4 = new Variable("p4"); - var predicate = Dnf.builder("TransitivePatternCall") + var p3 = Variable.of("p3"); + var p4 = Variable.of("p4"); + var predicate = Query.builder("TransitivePatternCall") .parameters(p3, p4) .clause( personView.call(p3), @@ -653,6 +679,7 @@ class QueryTest { var store = ModelStore.builder() .symbols(person, friend) .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) .queries(predicate) .build(); @@ -670,15 +697,71 @@ class QueryTest { friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0, 0), false, + Tuple.of(0, 1), true, + Tuple.of(0, 2), true, + Tuple.of(1, 0), false, + Tuple.of(1, 1), false, + Tuple.of(1, 2), true, + Tuple.of(2, 0), false, + Tuple.of(2, 1), false, + Tuple.of(2, 2), false, + Tuple.of(2, 3), false + ), predicateResultSet); + } + + @QueryEngineTest + void assumeTest(QueryEvaluationHint hint) { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder("Constraint") + .parameter(p1) + .clause( + personView.call(p1), + ageView.call(p1, x), + assume(greaterEq(x, constant(18))) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(hint) + .queries(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + ageInterpretation.put(Tuple.of(0), 12); + ageInterpretation.put(Tuple.of(1), 24); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), true, + Tuple.of(2), false + ), queryResultSet); } @Test void alwaysFalseTest() { var person = new Symbol<>("Person", 1, Boolean.class, false); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("AlwaysFalse").parameters(p1).build(); + var p1 = Variable.of("p1"); + var predicate = Query.builder("AlwaysFalse").parameters(p1).build(); var store = ModelStore.builder() .symbols(person) @@ -696,28 +779,19 @@ class QueryTest { personInterpretation.put(Tuple.of(2), true); queryEngine.flushChanges(); - assertEquals(0, predicateResultSet.countResults()); + assertResults(Map.of(), predicateResultSet); } @Test void alwaysTrueTest() { var person = new Symbol<>("Person", 1, Boolean.class, false); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("AlwaysTrue").parameters(p1).clause().build(); + var p1 = Variable.of("p1"); + var predicate = Query.builder("AlwaysTrue").parameters(p1).clause().build(); var storeBuilder = ModelStore.builder().symbols(person); var queryBuilder = storeBuilder.with(ViatraModelQuery.ADAPTER); assertThrows(IllegalArgumentException.class, () -> queryBuilder.queries(predicate)); } - - private static void compareMatchSets(Cursor cursor, Set expected) { - Set translatedMatchSet = new HashSet<>(); - while (cursor.move()) { - var element = cursor.getKey(); - translatedMatchSet.add(element.toTuple()); - } - assertEquals(expected, translatedMatchSet); - } } diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java index 461685b5..abd49341 100644 --- a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java @@ -1,25 +1,160 @@ package tools.refinery.store.query.viatra; +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import tools.refinery.store.model.ModelStore; -import tools.refinery.store.query.Dnf; import tools.refinery.store.query.ModelQuery; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Query; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.view.FilteredRelationView; +import tools.refinery.store.query.view.FunctionalRelationView; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import static org.junit.jupiter.api.Assertions.*; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults; +import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults; class QueryTransactionTest { @Test void flushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void localSearchTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void unrelatedChangesTest() { var person = new Symbol<>("Person", 1, Boolean.class, false); var asset = new Symbol<>("Asset", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); - var p1 = new Variable("p1"); - var predicate = Dnf.builder("TypeConstraint") + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") .parameters(p1) .clause(personView.call(p1)) .build(); @@ -36,7 +171,6 @@ class QueryTransactionTest { var queryEngine = model.getAdapter(ModelQuery.ADAPTER); var predicateResultSet = queryEngine.getResultSet(predicate); - assertEquals(0, predicateResultSet.countResults()); assertFalse(queryEngine.hasPendingChanges()); personInterpretation.put(Tuple.of(0), true); @@ -45,19 +179,245 @@ class QueryTransactionTest { assetInterpretation.put(Tuple.of(1), true); assetInterpretation.put(Tuple.of(2), true); - assertEquals(0, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), false, + Tuple.of(1), false, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); assertTrue(queryEngine.hasPendingChanges()); queryEngine.flushChanges(); - assertEquals(2, predicateResultSet.countResults()); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); assertFalse(queryEngine.hasPendingChanges()); - personInterpretation.put(Tuple.of(4), true); - assertEquals(2, predicateResultSet.countResults()); - assertTrue(queryEngine.hasPendingChanges()); + assetInterpretation.put(Tuple.of(3), true); + assertFalse(queryEngine.hasPendingChanges()); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false, + Tuple.of(4), false + ), predicateResultSet); + assertFalse(queryEngine.hasPendingChanges()); + } + + @Test + void tupleChangingChangeTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var ageView = new FunctionalRelationView<>(age); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder() + .parameter(p1) + .output(x) + .clause( + personView.call(p1), + ageView.call(p1, x) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .query(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + + ageInterpretation.put(Tuple.of(0), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), 24), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 25); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), 25), queryResultSet); + + ageInterpretation.put(Tuple.of(0), null); + + queryEngine.flushChanges(); + assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet); + } + + @Test + void tuplePreservingUnchangedTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var age = new Symbol<>("age", 1, Integer.class, null); + var personView = new KeyOnlyRelationView<>(person); + var adultView = new FilteredRelationView<>(age, "adult", n -> n != null && n >= 18); + + var p1 = Variable.of("p1"); + var x = Variable.of("x", Integer.class); + var query = Query.builder() + .parameter(p1) + .clause( + personView.call(p1), + adultView.call(p1) + ) + .build(); + + var store = ModelStore.builder() + .symbols(person, age) + .with(ViatraModelQuery.ADAPTER) + .query(query) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var ageInterpretation = model.getInterpretation(age); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var queryResultSet = queryEngine.getResultSet(query); + + personInterpretation.put(Tuple.of(0), true); + + ageInterpretation.put(Tuple.of(0), 24); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), true), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 25); + + queryEngine.flushChanges(); + assertResults(Map.of(Tuple.of(0), true), queryResultSet); + + ageInterpretation.put(Tuple.of(0), 17); queryEngine.flushChanges(); - assertEquals(3, predicateResultSet.countResults()); + assertResults(Map.of(Tuple.of(0), false), queryResultSet); + } + + @Disabled("TODO Fix DiffCursor") + @Test + void commitAfterFlushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + + var state1 = model.commit(); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + queryEngine.flushChanges(); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), false, + Tuple.of(2), true, + Tuple.of(3), false + ), predicateResultSet); + + model.restore(state1); + + assertFalse(queryEngine.hasPendingChanges()); + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); + } + + @Disabled("TODO Fix DiffCursor") + @Test + void commitWithoutFlushTest() { + var person = new Symbol<>("Person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + + var p1 = Variable.of("p1"); + var predicate = Query.builder("TypeConstraint") + .parameters(p1) + .clause(personView.call(p1)) + .build(); + + var store = ModelStore.builder() + .symbols(person) + .with(ViatraModelQuery.ADAPTER) + .queries(predicate) + .build(); + + var model = store.createEmptyModel(); + var personInterpretation = model.getInterpretation(person); + var queryEngine = model.getAdapter(ModelQuery.ADAPTER); + var predicateResultSet = queryEngine.getResultSet(predicate); + + personInterpretation.put(Tuple.of(0), true); + personInterpretation.put(Tuple.of(1), true); + + assertResults(Map.of(), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + var state1 = model.commit(); + + personInterpretation.put(Tuple.of(1), false); + personInterpretation.put(Tuple.of(2), true); + + assertResults(Map.of(), predicateResultSet); + assertTrue(queryEngine.hasPendingChanges()); + + model.restore(state1); + + assertResults(Map.of( + Tuple.of(0), true, + Tuple.of(1), true, + Tuple.of(2), false, + Tuple.of(3), false + ), predicateResultSet); assertFalse(queryEngine.hasPendingChanges()); } } diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java new file mode 100644 index 00000000..6f50ec73 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java @@ -0,0 +1,52 @@ +package tools.refinery.store.query.viatra.tests; + +import org.junit.jupiter.api.function.Executable; +import tools.refinery.store.query.ResultSet; +import tools.refinery.store.tuple.Tuple; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertAll; + +public final class QueryAssertions { + private QueryAssertions() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static void assertNullableResults(Map> expected, ResultSet resultSet) { + var nullableValuesMap = new LinkedHashMap(expected.size()); + for (var entry : expected.entrySet()) { + nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null)); + } + assertResults(nullableValuesMap, resultSet); + } + + public static void assertResults(Map expected, ResultSet resultSet) { + var defaultValue = resultSet.getQuery().defaultValue(); + var filteredExpected = new LinkedHashMap(); + var executables = new ArrayList(); + for (var entry : expected.entrySet()) { + var key = entry.getKey(); + var value = entry.getValue(); + if (!Objects.equals(value, defaultValue)) { + filteredExpected.put(key, value); + } + executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value))); + } + executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size()))); + + var actual = new LinkedHashMap(); + var cursor = resultSet.getAll(); + while (cursor.move()) { + var key = cursor.getKey(); + var previous = actual.put(key.toTuple(), cursor.getValue()); + assertThat("duplicate value for key " + key, previous, nullValue()); + } + executables.add(() -> assertThat("results cursor", actual, is(filteredExpected))); + + assertAll(executables); + } +} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java new file mode 100644 index 00000000..b1818a17 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java @@ -0,0 +1,22 @@ +package tools.refinery.store.query.viatra.tests; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; + +/** + * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. + */ +class QueryBackendHint extends QueryEvaluationHint { + public QueryBackendHint(BackendRequirement backendRequirementType) { + super(null, backendRequirementType); + } + + @Override + public String toString() { + return switch (getQueryBackendRequirementType()) { + case UNSPECIFIED -> "default"; + case DEFAULT_CACHING -> "incremental"; + case DEFAULT_SEARCH -> "localSearch"; + default -> throw new IllegalStateException("Unknown BackendRequirement"); + }; + } +} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java new file mode 100644 index 00000000..f129520c --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.viatra.tests; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@ParameterizedTest(name = "backend = {0}") +@ArgumentsSource(QueryEvaluationHintSource.class) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface QueryEngineTest { +} diff --git a/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java new file mode 100644 index 00000000..a55762e2 --- /dev/null +++ b/subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java @@ -0,0 +1,19 @@ +package tools.refinery.store.query.viatra.tests; + +import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +import java.util.stream.Stream; + +public class QueryEvaluationHintSource implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)), + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)), + Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH)) + ); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java new file mode 100644 index 00000000..6d411212 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query; + +import tools.refinery.store.query.dnf.AnyQuery; + +public sealed interface AnyResultSet permits ResultSet { + ModelQueryAdapter getAdapter(); + + AnyQuery getQuery(); + + int size(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java new file mode 100644 index 00000000..cec4c19f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java @@ -0,0 +1,65 @@ +package tools.refinery.store.query; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.literal.*; +import tools.refinery.store.query.term.*; + +import java.util.List; + +public interface Constraint { + String name(); + + List getSorts(); + + default int arity() { + return getSorts().size(); + } + + default boolean invalidIndex(int i) { + return i < 0 || i >= arity(); + } + + default LiteralReduction getReduction() { + return LiteralReduction.NOT_REDUCIBLE; + } + + default boolean equals(LiteralEqualityHelper helper, Constraint other) { + return equals(other); + } + + String toReferenceString(); + + default CallLiteral call(CallPolarity polarity, List arguments) { + return new CallLiteral(polarity, this, arguments); + } + + default CallLiteral call(CallPolarity polarity, Variable... arguments) { + return call(polarity, List.of(arguments)); + } + + default CallLiteral call(Variable... arguments) { + return call(CallPolarity.POSITIVE, arguments); + } + + default CallLiteral callTransitive(NodeVariable left, NodeVariable right) { + return call(CallPolarity.TRANSITIVE, List.of(left, right)); + } + + default AssignedValue count(List arguments) { + return targetVariable -> new CountLiteral(targetVariable, this, arguments); + } + + default AssignedValue count(Variable... arguments) { + return count(List.of(arguments)); + } + + default AssignedValue aggregate(DataVariable inputVariable, Aggregator aggregator, + List arguments) { + return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments); + } + + default AssignedValue aggregate(DataVariable inputVariable, Aggregator aggregator, + Variable... arguments) { + return aggregate(inputVariable, aggregator, List.of(arguments)); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java deleted file mode 100644 index b6744b50..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/Dnf.java +++ /dev/null @@ -1,175 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.query.equality.DnfEqualityChecker; -import tools.refinery.store.query.equality.LiteralEqualityHelper; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.DnfCallLiteral; -import tools.refinery.store.query.literal.LiteralReduction; - -import java.util.*; - -public final class Dnf implements RelationLike { - private static final String INDENTATION = " "; - - private final String name; - - private final String uniqueName; - - private final List parameters; - - private final List> functionalDependencies; - - private final List clauses; - - Dnf(String name, List parameters, List> functionalDependencies, - List clauses) { - validateFunctionalDependencies(parameters, functionalDependencies); - this.name = name; - this.uniqueName = DnfUtils.generateUniqueName(name); - this.parameters = parameters; - this.functionalDependencies = functionalDependencies; - this.clauses = clauses; - } - - private static void validateFunctionalDependencies( - Collection parameters, Collection> functionalDependencies) { - var parameterSet = new HashSet<>(parameters); - for (var functionalDependency : functionalDependencies) { - validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency); - validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency); - } - } - - private static void validateParameters(Collection parameters, Set parameterSet, - Collection toValidate, - FunctionalDependency functionalDependency) { - for (var variable : toValidate) { - if (!parameterSet.contains(variable)) { - throw new IllegalArgumentException( - "Variable %s of functional dependency %s does not appear in the parameter list %s" - .formatted(variable, functionalDependency, parameters)); - } - } - } - - @Override - public String name() { - return name == null ? uniqueName : name; - } - - public boolean isExplicitlyNamed() { - return name == null; - } - - public String getUniqueName() { - return uniqueName; - } - - public List getParameters() { - return parameters; - } - - public List> getFunctionalDependencies() { - return functionalDependencies; - } - - @Override - public int arity() { - return parameters.size(); - } - - public List getClauses() { - return clauses; - } - - public LiteralReduction getReduction() { - if (clauses.isEmpty()) { - return LiteralReduction.ALWAYS_FALSE; - } - for (var clause : clauses) { - if (clause.literals().isEmpty()) { - return LiteralReduction.ALWAYS_TRUE; - } - } - return LiteralReduction.NOT_REDUCIBLE; - } - - public DnfCallLiteral call(CallPolarity polarity, List arguments) { - return new DnfCallLiteral(polarity, this, arguments); - } - - public DnfCallLiteral call(CallPolarity polarity, Variable... arguments) { - return call(polarity, List.of(arguments)); - } - - public DnfCallLiteral call(Variable... arguments) { - return call(CallPolarity.POSITIVE, arguments); - } - - public DnfCallLiteral callTransitive(Variable left, Variable right) { - return call(CallPolarity.TRANSITIVE, List.of(left, right)); - } - - public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) { - if (arity() != other.arity()) { - return false; - } - int numClauses = clauses.size(); - if (numClauses != other.clauses.size()) { - return false; - } - for (int i = 0; i < numClauses; i++) { - var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, parameters, other.parameters); - if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { - return false; - } - } - return true; - } - - @Override - public String toString() { - var builder = new StringBuilder(); - builder.append("pred ").append(name()).append("("); - var parameterIterator = parameters.iterator(); - if (parameterIterator.hasNext()) { - builder.append(parameterIterator.next()); - while (parameterIterator.hasNext()) { - builder.append(", ").append(parameterIterator.next()); - } - } - builder.append(") <->"); - var clauseIterator = clauses.iterator(); - if (clauseIterator.hasNext()) { - appendClause(clauseIterator.next(), builder); - while (clauseIterator.hasNext()) { - builder.append("\n;"); - appendClause(clauseIterator.next(), builder); - } - } else { - builder.append("\n").append(INDENTATION).append(""); - } - builder.append(".\n"); - return builder.toString(); - } - - private static void appendClause(DnfClause clause, StringBuilder builder) { - var iterator = clause.literals().iterator(); - if (!iterator.hasNext()) { - builder.append("\n").append(INDENTATION).append(""); - return; - } - builder.append("\n").append(INDENTATION).append(iterator.next()); - while (iterator.hasNext()) { - builder.append(",\n").append(INDENTATION).append(iterator.next()); - } - } - - public static DnfBuilder builder() { - return builder(null); - } - - public static DnfBuilder builder(String name) { - return new DnfBuilder(name); - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java deleted file mode 100644 index ca47e979..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfBuilder.java +++ /dev/null @@ -1,115 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.query.literal.Literal; - -import java.util.*; - -@SuppressWarnings("UnusedReturnValue") -public class DnfBuilder { - private final String name; - - private final List parameters = new ArrayList<>(); - - private final List> functionalDependencies = new ArrayList<>(); - - private final List> clauses = new ArrayList<>(); - - DnfBuilder(String name) { - this.name = name; - } - - public DnfBuilder parameter(Variable variable) { - parameters.add(variable); - return this; - } - - public DnfBuilder parameters(Variable... variables) { - return parameters(List.of(variables)); - } - - public DnfBuilder parameters(Collection variables) { - parameters.addAll(variables); - return this; - } - - public DnfBuilder functionalDependencies(Collection> functionalDependencies) { - this.functionalDependencies.addAll(functionalDependencies); - return this; - } - - public DnfBuilder functionalDependency(FunctionalDependency functionalDependency) { - functionalDependencies.add(functionalDependency); - return this; - } - - public DnfBuilder functionalDependency(Set forEach, Set unique) { - return functionalDependency(new FunctionalDependency<>(forEach, unique)); - } - - public DnfBuilder clause(Literal... literals) { - clause(List.of(literals)); - return this; - } - - public DnfBuilder clause(Collection literals) { - // Remove duplicates by using a hashed data structure. - var filteredLiterals = new LinkedHashSet(literals.size()); - for (var literal : literals) { - var reduction = literal.getReduction(); - switch (reduction) { - case NOT_REDUCIBLE -> filteredLiterals.add(literal); - case ALWAYS_TRUE -> { - // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at - // least on object. - } - case ALWAYS_FALSE -> { - // Clauses with {@code false} literals can be omitted entirely. - return this; - } - default -> throw new IllegalArgumentException("Invalid reduction: " + reduction); - } - } - clauses.add(List.copyOf(filteredLiterals)); - return this; - } - - public DnfBuilder clause(DnfClause clause) { - return clause(clause.literals()); - } - - public DnfBuilder clauses(DnfClause... clauses) { - return clauses(List.of(clauses)); - } - - public DnfBuilder clauses(Collection clauses) { - for (var clause : clauses) { - this.clause(clause); - } - return this; - } - - public Dnf build() { - var postProcessedClauses = postProcessClauses(); - return new Dnf(name, Collections.unmodifiableList(parameters), - Collections.unmodifiableList(functionalDependencies), - Collections.unmodifiableList(postProcessedClauses)); - } - - private List postProcessClauses() { - var postProcessedClauses = new ArrayList(clauses.size()); - for (var literals : clauses) { - if (literals.isEmpty()) { - // Predicate will always match, the other clauses are irrelevant. - return List.of(new DnfClause(Set.of(), List.of())); - } - var variables = new HashSet(); - for (var constraint : literals) { - constraint.collectAllVariables(variables); - } - parameters.forEach(variables::remove); - postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables), - Collections.unmodifiableList(literals))); - } - return postProcessedClauses; - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java deleted file mode 100644 index c6e8b8c9..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfClause.java +++ /dev/null @@ -1,22 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.query.equality.LiteralEqualityHelper; -import tools.refinery.store.query.literal.Literal; - -import java.util.List; -import java.util.Set; - -public record DnfClause(Set quantifiedVariables, List literals) { - public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) { - int size = literals.size(); - if (size != other.literals.size()) { - return false; - } - for (int i = 0; i < size; i++) { - if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) { - return false; - } - } - return true; - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java deleted file mode 100644 index c7a2849c..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/DnfUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package tools.refinery.store.query; - -import java.util.UUID; - -public final class DnfUtils { - private DnfUtils() { - throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); - } - - public static String generateUniqueName(String originalName) { - UUID uuid = UUID.randomUUID(); - String uniqueString = "_" + uuid.toString().replace('-', '_'); - if (originalName == null) { - return uniqueString; - } else { - return originalName + uniqueString; - } - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java index 9ff6df26..9af73bdd 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java @@ -2,21 +2,33 @@ package tools.refinery.store.query; import tools.refinery.store.map.Cursor; import tools.refinery.store.map.Cursors; +import tools.refinery.store.query.dnf.Query; import tools.refinery.store.tuple.TupleLike; -public class EmptyResultSet implements ResultSet { +public record EmptyResultSet(ModelQueryAdapter adapter, Query query) implements ResultSet { @Override - public boolean hasResult(TupleLike parameters) { - return false; + public ModelQueryAdapter getAdapter() { + return adapter; } @Override - public Cursor allResults() { + public Query getQuery() { + return query; + } + + @Override + public T get(TupleLike parameters) { + return query.defaultValue(); + } + + + @Override + public Cursor getAll() { return Cursors.empty(); } @Override - public int countResults() { + public int size() { return 0; } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java deleted file mode 100644 index 63a81713..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/FunctionalDependency.java +++ /dev/null @@ -1,15 +0,0 @@ -package tools.refinery.store.query; - -import java.util.HashSet; -import java.util.Set; - -public record FunctionalDependency(Set forEach, Set unique) { - public FunctionalDependency { - var uniqueForEach = new HashSet<>(unique); - uniqueForEach.retainAll(forEach); - if (!uniqueForEach.isEmpty()) { - throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" - .formatted(uniqueForEach)); - } - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java index f7762444..2e30fec4 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java @@ -1,11 +1,17 @@ package tools.refinery.store.query; import tools.refinery.store.adapter.ModelAdapter; +import tools.refinery.store.query.dnf.AnyQuery; +import tools.refinery.store.query.dnf.Query; public interface ModelQueryAdapter extends ModelAdapter { ModelQueryStoreAdapter getStoreAdapter(); - ResultSet getResultSet(Dnf query); + default AnyResultSet getResultSet(AnyQuery query) { + return getResultSet((Query) query); + } + + ResultSet getResultSet(Query query); boolean hasPendingChanges(); diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java index b3cfb4b4..4fdc9210 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java @@ -2,21 +2,23 @@ package tools.refinery.store.query; import tools.refinery.store.adapter.ModelAdapterBuilder; import tools.refinery.store.model.ModelStore; +import tools.refinery.store.query.dnf.AnyQuery; import java.util.Collection; import java.util.List; +@SuppressWarnings("UnusedReturnValue") public interface ModelQueryBuilder extends ModelAdapterBuilder { - default ModelQueryBuilder queries(Dnf... queries) { + default ModelQueryBuilder queries(AnyQuery... queries) { return queries(List.of(queries)); } - default ModelQueryBuilder queries(Collection queries) { + default ModelQueryBuilder queries(Collection queries) { queries.forEach(this::query); return this; } - ModelQueryBuilder query(Dnf query); + ModelQueryBuilder query(AnyQuery query); @Override ModelQueryStoreAdapter createStoreAdapter(ModelStore store); diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java index 091d6d06..514e582b 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java @@ -1,15 +1,16 @@ package tools.refinery.store.query; -import tools.refinery.store.query.view.AnyRelationView; import tools.refinery.store.adapter.ModelStoreAdapter; import tools.refinery.store.model.Model; +import tools.refinery.store.query.dnf.AnyQuery; +import tools.refinery.store.query.view.AnyRelationView; import java.util.Collection; public interface ModelQueryStoreAdapter extends ModelStoreAdapter { Collection getRelationViews(); - Collection getQueries(); + Collection getQueries(); @Override ModelQueryAdapter createModelAdapter(Model model); diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java deleted file mode 100644 index 8c784d8b..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/RelationLike.java +++ /dev/null @@ -1,11 +0,0 @@ -package tools.refinery.store.query; - -public interface RelationLike { - String name(); - - int arity(); - - default boolean invalidIndex(int i) { - return i < 0 || i >= arity(); - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java index d2b8c9dd..3f6bc06f 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java @@ -1,16 +1,13 @@ package tools.refinery.store.query; import tools.refinery.store.map.Cursor; +import tools.refinery.store.query.dnf.Query; import tools.refinery.store.tuple.TupleLike; -public interface ResultSet { - default boolean hasResult() { - return countResults() > 0; - } +public non-sealed interface ResultSet extends AnyResultSet { + Query getQuery(); - boolean hasResult(TupleLike parameters); + T get(TupleLike parameters); - Cursor allResults(); - - int countResults(); + Cursor getAll(); } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java deleted file mode 100644 index d0e0dead..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/Variable.java +++ /dev/null @@ -1,63 +0,0 @@ -package tools.refinery.store.query; - -import tools.refinery.store.query.literal.ConstantLiteral; -import tools.refinery.store.query.literal.EquivalenceLiteral; - -import java.util.Objects; - -public class Variable { - private final String name; - private final String uniqueName; - - public Variable() { - this(null); - } - - public Variable(String name) { - super(); - this.name = name; - this.uniqueName = DnfUtils.generateUniqueName(name); - - } - public String getName() { - return name == null ? uniqueName : name; - } - - public boolean isExplicitlyNamed() { - return name != null; - } - - public String getUniqueName() { - return uniqueName; - } - - public ConstantLiteral isConstant(int value) { - return new ConstantLiteral(this, value); - } - - public EquivalenceLiteral isEquivalent(Variable other) { - return new EquivalenceLiteral(true, this, other); - } - - public EquivalenceLiteral notEquivalent(Variable other) { - return new EquivalenceLiteral(false, this, other); - } - - @Override - public String toString() { - return getName(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Variable variable = (Variable) o; - return Objects.equals(uniqueName, variable.uniqueName); - } - - @Override - public int hashCode() { - return Objects.hash(uniqueName); - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java new file mode 100644 index 00000000..d0a2367f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query.dnf; + +public sealed interface AnyQuery permits Query { + String name(); + + int arity(); + + Class valueType(); + + Dnf getDnf(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java new file mode 100644 index 00000000..1b7759c7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java @@ -0,0 +1,194 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.equality.DnfEqualityChecker; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.literal.LiteralReduction; +import tools.refinery.store.query.term.Sort; +import tools.refinery.store.query.term.Variable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class Dnf implements Constraint { + private static final String INDENTATION = " "; + + private final String name; + + private final String uniqueName; + + private final List parameters; + + private final List> functionalDependencies; + + private final List clauses; + + Dnf(String name, List parameters, List> functionalDependencies, + List clauses) { + validateFunctionalDependencies(parameters, functionalDependencies); + this.name = name; + this.uniqueName = DnfUtils.generateUniqueName(name); + this.parameters = parameters; + this.functionalDependencies = functionalDependencies; + this.clauses = clauses; + } + + private static void validateFunctionalDependencies( + Collection parameters, Collection> functionalDependencies) { + var parameterSet = new HashSet<>(parameters); + for (var functionalDependency : functionalDependencies) { + validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency); + validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency); + } + } + + private static void validateParameters(Collection parameters, Set parameterSet, + Collection toValidate, + FunctionalDependency functionalDependency) { + for (var variable : toValidate) { + if (!parameterSet.contains(variable)) { + throw new IllegalArgumentException( + "Variable %s of functional dependency %s does not appear in the parameter list %s" + .formatted(variable, functionalDependency, parameters)); + } + } + } + + @Override + public String name() { + return name == null ? uniqueName : name; + } + + public boolean isExplicitlyNamed() { + return name == null; + } + + public String getUniqueName() { + return uniqueName; + } + + public List getParameters() { + return parameters; + } + + @Override + public List getSorts() { + return parameters.stream().map(Variable::getSort).toList(); + } + + public List> getFunctionalDependencies() { + return functionalDependencies; + } + + @Override + public int arity() { + return parameters.size(); + } + + public List getClauses() { + return clauses; + } + + public RelationalQuery asRelation() { + return new RelationalQuery(this); + } + + public FunctionalQuery asFunction(Class type) { + return new FunctionalQuery<>(this, type); + } + + @Override + public LiteralReduction getReduction() { + if (clauses.isEmpty()) { + return LiteralReduction.ALWAYS_FALSE; + } + for (var clause : clauses) { + if (clause.literals().isEmpty()) { + return LiteralReduction.ALWAYS_TRUE; + } + } + return LiteralReduction.NOT_REDUCIBLE; + } + + public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) { + if (arity() != other.arity()) { + return false; + } + int numClauses = clauses.size(); + if (numClauses != other.clauses.size()) { + return false; + } + for (int i = 0; i < numClauses; i++) { + var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, parameters, other.parameters); + if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) { + return false; + } + } + return true; + } + + @Override + public boolean equals(LiteralEqualityHelper helper, Constraint other) { + if (other instanceof Dnf otherDnf) { + return helper.dnfEqual(this, otherDnf); + } + return false; + } + + @Override + public String toString() { + return "%s/%d".formatted(name, arity()); + } + + @Override + public String toReferenceString() { + return "@Dnf " + name; + } + + public String toDefinitionString() { + var builder = new StringBuilder(); + builder.append("pred ").append(name()).append("("); + var parameterIterator = parameters.iterator(); + if (parameterIterator.hasNext()) { + builder.append(parameterIterator.next()); + while (parameterIterator.hasNext()) { + builder.append(", ").append(parameterIterator.next()); + } + } + builder.append(") <->"); + var clauseIterator = clauses.iterator(); + if (clauseIterator.hasNext()) { + appendClause(clauseIterator.next(), builder); + while (clauseIterator.hasNext()) { + builder.append("\n;"); + appendClause(clauseIterator.next(), builder); + } + } else { + builder.append("\n").append(INDENTATION).append(""); + } + builder.append(".\n"); + return builder.toString(); + } + + private static void appendClause(DnfClause clause, StringBuilder builder) { + var iterator = clause.literals().iterator(); + if (!iterator.hasNext()) { + builder.append("\n").append(INDENTATION).append(""); + return; + } + builder.append("\n").append(INDENTATION).append(iterator.next()); + while (iterator.hasNext()) { + builder.append(",\n").append(INDENTATION).append(iterator.next()); + } + } + + public static DnfBuilder builder() { + return builder(null); + } + + public static DnfBuilder builder(String name) { + return new DnfBuilder(name); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java new file mode 100644 index 00000000..aad5a85f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java @@ -0,0 +1,110 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.Variable; + +import java.util.*; + +@SuppressWarnings("UnusedReturnValue") +public final class DnfBuilder { + private final String name; + + private final List parameters = new ArrayList<>(); + + private final List> functionalDependencies = new ArrayList<>(); + + private final List> clauses = new ArrayList<>(); + + DnfBuilder(String name) { + this.name = name; + } + + public DnfBuilder parameter(Variable variable) { + if (parameters.contains(variable)) { + throw new IllegalArgumentException("Duplicate parameter: " + variable); + } + parameters.add(variable); + return this; + } + + public DnfBuilder parameters(Variable... variables) { + return parameters(List.of(variables)); + } + + public DnfBuilder parameters(Collection variables) { + parameters.addAll(variables); + return this; + } + + public DnfBuilder functionalDependencies(Collection> functionalDependencies) { + this.functionalDependencies.addAll(functionalDependencies); + return this; + } + + public DnfBuilder functionalDependency(FunctionalDependency functionalDependency) { + functionalDependencies.add(functionalDependency); + return this; + } + + public DnfBuilder functionalDependency(Set forEach, Set unique) { + return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique))); + } + + public DnfBuilder clause(Literal... literals) { + clause(List.of(literals)); + return this; + } + + public DnfBuilder clause(Collection literals) { + // Remove duplicates by using a hashed data structure. + var filteredLiterals = new LinkedHashSet(literals.size()); + for (var literal : literals) { + var reduction = literal.getReduction(); + switch (reduction) { + case NOT_REDUCIBLE -> filteredLiterals.add(literal); + case ALWAYS_TRUE -> { + // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at + // least on object. + } + case ALWAYS_FALSE -> { + // Clauses with {@code false} literals can be omitted entirely. + return this; + } + default -> throw new IllegalArgumentException("Invalid reduction: " + reduction); + } + } + clauses.add(List.copyOf(filteredLiterals)); + return this; + } + + public Dnf build() { + var postProcessedClauses = postProcessClauses(); + return new Dnf(name, Collections.unmodifiableList(parameters), + Collections.unmodifiableList(functionalDependencies), + Collections.unmodifiableList(postProcessedClauses)); + } + + void output(DataVariable outputVariable) { + functionalDependency(Set.copyOf(parameters), Set.of(outputVariable)); + parameter(outputVariable); + } + + private List postProcessClauses() { + var postProcessedClauses = new ArrayList(clauses.size()); + for (var literals : clauses) { + if (literals.isEmpty()) { + // Predicate will always match, the other clauses are irrelevant. + return List.of(new DnfClause(Set.of(), List.of())); + } + var variables = new HashSet(); + for (var literal : literals) { + variables.addAll(literal.getBoundVariables()); + } + parameters.forEach(variables::remove); + postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables), + Collections.unmodifiableList(literals))); + } + return postProcessedClauses; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java new file mode 100644 index 00000000..01830af1 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.term.Variable; + +import java.util.List; +import java.util.Set; + +public record DnfClause(Set boundVariables, List literals) { + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) { + int size = literals.size(); + if (size != other.literals.size()) { + return false; + } + for (int i = 0; i < size; i++) { + if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) { + return false; + } + } + return true; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java new file mode 100644 index 00000000..9bcf944c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java @@ -0,0 +1,19 @@ +package tools.refinery.store.query.dnf; + +import java.util.UUID; + +public final class DnfUtils { + private DnfUtils() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static String generateUniqueName(String originalName) { + UUID uuid = UUID.randomUUID(); + String uniqueString = "_" + uuid.toString().replace('-', '_'); + if (originalName == null) { + return uniqueString; + } else { + return originalName + uniqueString; + } + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java new file mode 100644 index 00000000..f4cd109f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java @@ -0,0 +1,15 @@ +package tools.refinery.store.query.dnf; + +import java.util.HashSet; +import java.util.Set; + +public record FunctionalDependency(Set forEach, Set unique) { + public FunctionalDependency { + var uniqueForEach = new HashSet<>(unique); + uniqueForEach.retainAll(forEach); + if (!uniqueForEach.isEmpty()) { + throw new IllegalArgumentException("Variables %s appear on both sides of the functional dependency" + .formatted(uniqueForEach)); + } + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java new file mode 100644 index 00000000..5bf6f8c5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java @@ -0,0 +1,103 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.literal.CallPolarity; +import tools.refinery.store.query.term.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public final class FunctionalQuery implements Query { + private final Dnf dnf; + private final Class type; + + FunctionalQuery(Dnf dnf, Class type) { + var parameters = dnf.getParameters(); + int outputIndex = dnf.arity() - 1; + for (int i = 0; i < outputIndex; i++) { + var parameter = parameters.get(i); + if (!(parameter instanceof NodeVariable)) { + throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead" + .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort())); + } + } + var outputParameter = parameters.get(outputIndex); + if (!(outputParameter instanceof DataVariable dataOutputParameter) || + !dataOutputParameter.getType().equals(type)) { + throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead" + .formatted(outputParameter, dnf, type, outputParameter.getSort())); + } + this.dnf = dnf; + this.type = type; + } + + @Override + public String name() { + return dnf.name(); + } + + @Override + public int arity() { + return dnf.arity() - 1; + } + + @Override + public Class valueType() { + return type; + } + + @Override + public T defaultValue() { + return null; + } + + @Override + public Dnf getDnf() { + return dnf; + } + + public AssignedValue call(List arguments) { + return targetVariable -> { + var argumentsWithTarget = new ArrayList(arguments.size() + 1); + argumentsWithTarget.addAll(arguments); + argumentsWithTarget.add(targetVariable); + return dnf.call(CallPolarity.POSITIVE, argumentsWithTarget); + }; + } + + public AssignedValue call(NodeVariable... arguments) { + return call(List.of(arguments)); + } + + public AssignedValue aggregate(Aggregator aggregator, List arguments) { + return targetVariable -> { + var placeholderVariable = Variable.of(type); + var argumentsWithPlaceholder = new ArrayList(arguments.size() + 1); + argumentsWithPlaceholder.addAll(arguments); + argumentsWithPlaceholder.add(placeholderVariable); + return dnf.aggregate(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable); + }; + } + + public AssignedValue aggregate(Aggregator aggregator, NodeVariable... arguments) { + return aggregate(aggregator, List.of(arguments)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FunctionalQuery that = (FunctionalQuery) o; + return dnf.equals(that.dnf) && type.equals(that.type); + } + + @Override + public int hashCode() { + return Objects.hash(dnf, type); + } + + @Override + public String toString() { + return dnf.toString(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java new file mode 100644 index 00000000..ca2bc006 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java @@ -0,0 +1,46 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.term.Variable; + +import java.util.Collection; +import java.util.Set; + +public final class FunctionalQueryBuilder { + private final DnfBuilder dnfBuilder; + private final Class type; + + FunctionalQueryBuilder(DnfBuilder dnfBuilder, Class type) { + this.dnfBuilder = dnfBuilder; + this.type = type; + } + + public FunctionalQueryBuilder functionalDependencies(Collection> functionalDependencies) { + dnfBuilder.functionalDependencies(functionalDependencies); + return this; + } + + public FunctionalQueryBuilder functionalDependency(FunctionalDependency functionalDependency) { + dnfBuilder.functionalDependency(functionalDependency); + return this; + } + + public FunctionalQueryBuilder functionalDependency(Set forEach, Set unique) { + dnfBuilder.functionalDependency(forEach, unique); + return this; + } + + public FunctionalQueryBuilder clause(Literal... literals) { + dnfBuilder.clause(literals); + return this; + } + + public FunctionalQueryBuilder clause(Collection literals) { + dnfBuilder.clause(literals); + return this; + } + + public FunctionalQuery build() { + return dnfBuilder.build().asFunction(type); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java new file mode 100644 index 00000000..32e33052 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.dnf; + +public sealed interface Query extends AnyQuery permits RelationalQuery, FunctionalQuery { + @Override + Class valueType(); + + T defaultValue(); + + static QueryBuilder builder() { + return new QueryBuilder(); + } + + static QueryBuilder builder(String name) { + return new QueryBuilder(name); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java new file mode 100644 index 00000000..ed253cc9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java @@ -0,0 +1,71 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.NodeVariable; +import tools.refinery.store.query.term.Variable; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public final class QueryBuilder { + private final DnfBuilder dnfBuilder; + + QueryBuilder(String name) { + dnfBuilder = Dnf.builder(name); + } + + QueryBuilder() { + dnfBuilder = Dnf.builder(); + } + + public QueryBuilder parameter(NodeVariable variable) { + dnfBuilder.parameter(variable); + return this; + } + + public QueryBuilder parameters(NodeVariable... variables) { + dnfBuilder.parameters(variables); + return this; + } + + public QueryBuilder parameters(List variables) { + dnfBuilder.parameters(variables); + return this; + } + + public FunctionalQueryBuilder output(DataVariable outputVariable) { + dnfBuilder.output(outputVariable); + return new FunctionalQueryBuilder<>(dnfBuilder, outputVariable.getType()); + } + + public QueryBuilder functionalDependencies(Collection> functionalDependencies) { + dnfBuilder.functionalDependencies(functionalDependencies); + return this; + } + + public QueryBuilder functionalDependency(FunctionalDependency functionalDependency) { + dnfBuilder.functionalDependency(functionalDependency); + return this; + } + + public QueryBuilder functionalDependency(Set forEach, Set unique) { + dnfBuilder.functionalDependency(forEach, unique); + return this; + } + + public QueryBuilder clause(Literal... literals) { + dnfBuilder.clause(literals); + return this; + } + + public QueryBuilder clause(Collection literals) { + dnfBuilder.clause(literals); + return this; + } + + public RelationalQuery build() { + return dnfBuilder.build().asRelation(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java new file mode 100644 index 00000000..5307e509 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java @@ -0,0 +1,93 @@ +package tools.refinery.store.query.dnf; + +import tools.refinery.store.query.literal.CallLiteral; +import tools.refinery.store.query.literal.CallPolarity; +import tools.refinery.store.query.term.AssignedValue; +import tools.refinery.store.query.term.NodeSort; +import tools.refinery.store.query.term.NodeVariable; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public final class RelationalQuery implements Query { + private final Dnf dnf; + + RelationalQuery(Dnf dnf) { + for (var parameter : dnf.getParameters()) { + if (!(parameter instanceof NodeVariable)) { + throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead" + .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort())); + } + } + this.dnf = dnf; + } + + @Override + public String name() { + return dnf.name(); + } + + @Override + public int arity() { + return dnf.arity(); + } + + @Override + public Class valueType() { + return Boolean.class; + } + + @Override + public Boolean defaultValue() { + return false; + } + + @Override + public Dnf getDnf() { + return dnf; + } + + public CallLiteral call(CallPolarity polarity, List arguments) { + return dnf.call(polarity, Collections.unmodifiableList(arguments)); + } + + public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) { + return dnf.call(polarity, arguments); + } + + public CallLiteral call(NodeVariable... arguments) { + return dnf.call(arguments); + } + + public CallLiteral callTransitive(NodeVariable left, NodeVariable right) { + return dnf.callTransitive(left, right); + } + + public AssignedValue count(List arguments) { + return dnf.count(Collections.unmodifiableList(arguments)); + } + + public AssignedValue count(NodeVariable... arguments) { + return dnf.count(arguments); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RelationalQuery that = (RelationalQuery) o; + return dnf.equals(that.dnf); + } + + @Override + public int hashCode() { + return Objects.hash(dnf); + } + + @Override + public String toString() { + return dnf.toString(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java index ebd7f5b0..c3bc3ea3 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java @@ -1,6 +1,6 @@ package tools.refinery.store.query.equality; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.util.CycleDetectingMapper; import java.util.List; diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java index eb77de17..6b1f2076 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java @@ -1,6 +1,6 @@ package tools.refinery.store.query.equality; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; @FunctionalInterface public interface DnfEqualityChecker { diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java index 23f1acc7..07d261ea 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java @@ -1,7 +1,7 @@ package tools.refinery.store.query.equality; -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.term.Variable; import java.util.HashMap; import java.util.List; diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java new file mode 100644 index 00000000..657ca26b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java @@ -0,0 +1,80 @@ +package tools.refinery.store.query.literal; + +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Variable; + +import java.util.List; +import java.util.Objects; + +public abstract class AbstractCallLiteral implements Literal { + private final Constraint target; + private final List arguments; + + protected AbstractCallLiteral(Constraint target, List arguments) { + int arity = target.arity(); + if (arguments.size() != arity) { + throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), + target.arity(), arguments.size())); + } + this.target = target; + this.arguments = arguments; + var sorts = target.getSorts(); + for (int i = 0; i < arity; i++) { + var argument = arguments.get(i); + var sort = sorts.get(i); + if (!sort.isInstance(argument)) { + throw new IllegalArgumentException("Required argument %d of %s to be of sort %s, but got %s instead" + .formatted(i, target, sort, argument.getSort())); + } + } + } + + public Constraint getTarget() { + return target; + } + + public List getArguments() { + return arguments; + } + + @Override + public Literal substitute(Substitution substitution) { + var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList(); + return doSubstitute(substitution, substitutedArguments); + } + + protected abstract Literal doSubstitute(Substitution substitution, List substitutedArguments); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + var otherCallLiteral = (AbstractCallLiteral) other; + var arity = arguments.size(); + if (arity != otherCallLiteral.arguments.size()) { + return false; + } + for (int i = 0; i < arity; i++) { + if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) { + return false; + } + } + return target.equals(helper, otherCallLiteral.target); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractCallLiteral that = (AbstractCallLiteral) o; + return target.equals(that.target) && arguments.equals(that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(target, arguments); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java new file mode 100644 index 00000000..df64839c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java @@ -0,0 +1,113 @@ +package tools.refinery.store.query.literal; + +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Aggregator; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.Variable; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class AggregationLiteral extends AbstractCallLiteral { + private final DataVariable resultVariable; + private final DataVariable inputVariable; + private final Aggregator aggregator; + + public AggregationLiteral(DataVariable resultVariable, Aggregator aggregator, + DataVariable inputVariable, Constraint target, List arguments) { + super(target, arguments); + if (!inputVariable.getType().equals(aggregator.getInputType())) { + throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted( + inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName())); + } + if (!resultVariable.getType().equals(aggregator.getResultType())) { + throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted( + resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName())); + } + if (!arguments.contains(inputVariable)) { + throw new IllegalArgumentException("Input variable %s must appear in the argument list".formatted( + inputVariable)); + } + if (arguments.contains(resultVariable)) { + throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted( + resultVariable)); + } + this.resultVariable = resultVariable; + this.inputVariable = inputVariable; + this.aggregator = aggregator; + } + + public DataVariable getResultVariable() { + return resultVariable; + } + + public DataVariable getInputVariable() { + return inputVariable; + } + + public Aggregator getAggregator() { + return aggregator; + } + + @Override + public Set getBoundVariables() { + return Set.of(resultVariable); + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator, + substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherAggregationLiteral = (AggregationLiteral) other; + return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) && + aggregator.equals(otherAggregationLiteral.aggregator) && + helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + AggregationLiteral that = (AggregationLiteral) o; + return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) && + aggregator.equals(that.aggregator); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(resultVariable); + builder.append(" is "); + builder.append(getTarget().toReferenceString()); + builder.append("("); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + var argument = argumentIterator.next(); + if (inputVariable.equals(argument)) { + builder.append("@Aggregate(\"").append(aggregator).append("\") "); + } + builder.append(argument); + while (argumentIterator.hasNext()) { + builder.append(", ").append(argumentIterator.next()); + } + } + builder.append(")"); + return builder.toString(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java new file mode 100644 index 00000000..52ac42d7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java @@ -0,0 +1,44 @@ +package tools.refinery.store.query.literal; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.Variable; + +import java.util.Set; + +public record AssignLiteral(DataVariable variable, Term term) implements Literal { + public AssignLiteral { + if (!term.getType().equals(variable.getType())) { + throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( + term, variable.getType().getName(), term.getType().getName())); + } + } + + @Override + public Set getBoundVariables() { + return Set.of(variable); + } + + @Override + public Literal substitute(Substitution substitution) { + return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + var otherLetLiteral = (AssignLiteral) other; + return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper, + otherLetLiteral.term); + } + + + @Override + public String toString() { + return "%s is (%s)".formatted(variable, term); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java new file mode 100644 index 00000000..0b4267b4 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java @@ -0,0 +1,53 @@ +package tools.refinery.store.query.literal; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.term.bool.BoolConstantTerm; + +import java.util.Set; + +public record AssumeLiteral(Term term) implements Literal { + public AssumeLiteral { + if (!term.getType().equals(Boolean.class)) { + throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted( + term, Boolean.class.getName(), term.getType().getName())); + } + } + + @Override + public Set getBoundVariables() { + return Set.of(); + } + + @Override + public Literal substitute(Substitution substitution) { + return new AssumeLiteral(term.substitute(substitution)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (other == null || getClass() != other.getClass()) { + return false; + } + var otherAssumeLiteral = (AssumeLiteral) other; + return term.equalsWithSubstitution(helper, otherAssumeLiteral.term); + } + + @Override + public LiteralReduction getReduction() { + if (BoolConstantTerm.TRUE.equals(term)) { + return LiteralReduction.ALWAYS_TRUE; + } else if (BoolConstantTerm.FALSE.equals(term)) { + return LiteralReduction.ALWAYS_FALSE; + } else { + return LiteralReduction.NOT_REDUCIBLE; + } + } + + @Override + public String toString() { + return "(%s)".formatted(term); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java index 6d751be8..38be61a4 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java @@ -1,12 +1,12 @@ package tools.refinery.store.query.literal; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.equality.LiteralEqualityHelper; import tools.refinery.store.query.substitution.Substitution; import java.util.Set; -public enum BooleanLiteral implements PolarLiteral { +public enum BooleanLiteral implements CanNegate { TRUE(true), FALSE(false); @@ -17,8 +17,8 @@ public enum BooleanLiteral implements PolarLiteral { } @Override - public void collectAllVariables(Set variables) { - // No variables to collect. + public Set getBoundVariables() { + return Set.of(); } @Override diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java index 091b4e04..78fae7f5 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java @@ -1,109 +1,78 @@ package tools.refinery.store.query.literal; -import tools.refinery.store.query.RelationLike; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.Constraint; import tools.refinery.store.query.equality.LiteralEqualityHelper; import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.NodeSort; +import tools.refinery.store.query.term.Variable; import java.util.List; import java.util.Objects; import java.util.Set; -public abstract class CallLiteral implements Literal { +public final class CallLiteral extends AbstractCallLiteral implements CanNegate { private final CallPolarity polarity; - private final T target; - private final List arguments; - protected CallLiteral(CallPolarity polarity, T target, List arguments) { - if (arguments.size() != target.arity()) { - throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(), - target.arity(), arguments.size())); - } - if (polarity.isTransitive() && target.arity() != 2) { - throw new IllegalArgumentException("Transitive closures can only take binary relations"); + public CallLiteral(CallPolarity polarity, Constraint target, List arguments) { + super(target, arguments); + if (polarity.isTransitive()) { + if (target.arity() != 2) { + throw new IllegalArgumentException("Transitive closures can only take binary relations"); + } + var sorts = target.getSorts(); + if (!sorts.get(0).equals(NodeSort.INSTANCE) || !sorts.get(1).equals(NodeSort.INSTANCE)) { + throw new IllegalArgumentException("Transitive closures can only be computed over nodes"); + } } this.polarity = polarity; - this.target = target; - this.arguments = arguments; } public CallPolarity getPolarity() { return polarity; } - public abstract Class getTargetType(); - - public T getTarget() { - return target; - } - - public List getArguments() { - return arguments; - } - @Override - public void collectAllVariables(Set variables) { - if (polarity.isPositive()) { - variables.addAll(arguments); - } + public Set getBoundVariables() { + return polarity.isPositive() ? Set.copyOf(getArguments()) : Set.of(); } - protected List substituteArguments(Substitution substitution) { - return arguments.stream().map(substitution::getSubstitute).toList(); + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new CallLiteral(polarity, getTarget(), substitutedArguments); } - /** - * Compares the target of this call literal with another object. - * - * @param helper Equality helper for comparing {@link Variable} and {@link tools.refinery.store.query.Dnf} - * instances. - * @param otherTarget The object to compare the target to. - * @return {@code true} if {@code otherTarget} is equal to the return value of {@link #getTarget()} according to - * {@code helper}, {@code false} otherwise. - */ - protected boolean targetEquals(LiteralEqualityHelper helper, T otherTarget) { - return target.equals(otherTarget); + @Override + public LiteralReduction getReduction() { + var reduction = getTarget().getReduction(); + return polarity.isPositive() ? reduction : reduction.negate(); } @Override public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { - if (other.getClass() != getClass()) { + if (!super.equalsWithSubstitution(helper, other)) { return false; } - var otherCallLiteral = (CallLiteral) other; - if (getTargetType() != otherCallLiteral.getTargetType() || polarity != otherCallLiteral.polarity) { - return false; - } - var arity = arguments.size(); - if (arity != otherCallLiteral.arguments.size()) { - return false; - } - for (int i = 0; i < arity; i++) { - if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) { - return false; - } - } - @SuppressWarnings("unchecked") - var otherTarget = (T) otherCallLiteral.target; - return targetEquals(helper, otherTarget); + var otherCallLiteral = (CallLiteral) other; + return polarity.equals(otherCallLiteral.polarity); + } + + @Override + public CallLiteral negate() { + return new CallLiteral(polarity.negate(), getTarget(), getArguments()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - CallLiteral callAtom = (CallLiteral) o; - return polarity == callAtom.polarity && Objects.equals(target, callAtom.target) && - Objects.equals(arguments, callAtom.arguments); + if (!super.equals(o)) return false; + CallLiteral that = (CallLiteral) o; + return polarity == that.polarity; } @Override public int hashCode() { - return Objects.hash(polarity, target, arguments); - } - - protected String targetToString() { - return "@%s %s".formatted(getTargetType().getSimpleName(), target.name()); + return Objects.hash(super.hashCode(), polarity); } @Override @@ -112,12 +81,12 @@ public abstract class CallLiteral implements Literal { if (!polarity.isPositive()) { builder.append("!("); } - builder.append(targetToString()); + builder.append(getTarget().toReferenceString()); if (polarity.isTransitive()) { builder.append("+"); } builder.append("("); - var argumentIterator = arguments.iterator(); + var argumentIterator = getArguments().iterator(); if (argumentIterator.hasNext()) { builder.append(argumentIterator.next()); while (argumentIterator.hasNext()) { diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java new file mode 100644 index 00000000..3e159c43 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java @@ -0,0 +1,5 @@ +package tools.refinery.store.query.literal; + +public interface CanNegate> extends Literal { + T negate(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java index d01c7d20..93fa3df0 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java @@ -1,20 +1,21 @@ package tools.refinery.store.query.literal; -import tools.refinery.store.query.Variable; import tools.refinery.store.query.equality.LiteralEqualityHelper; import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.NodeVariable; +import tools.refinery.store.query.term.Variable; import java.util.Set; -public record ConstantLiteral(Variable variable, int nodeId) implements Literal { +public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal { @Override - public void collectAllVariables(Set variables) { - variables.add(variable); + public Set getBoundVariables() { + return Set.of(variable); } @Override public ConstantLiteral substitute(Substitution substitution) { - return new ConstantLiteral(substitution.getSubstitute(variable), nodeId); + return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId); } @Override diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java new file mode 100644 index 00000000..32e7ba3a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java @@ -0,0 +1,83 @@ +package tools.refinery.store.query.literal; + +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.Variable; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class CountLiteral extends AbstractCallLiteral { + private final DataVariable resultVariable; + + public CountLiteral(DataVariable resultVariable, Constraint target, List arguments) { + super(target, arguments); + if (!resultVariable.getType().equals(Integer.class)) { + throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted( + resultVariable, Integer.class.getName(), resultVariable.getType().getName())); + } + if (arguments.contains(resultVariable)) { + throw new IllegalArgumentException("Count result variable %s must not appear in the argument list" + .formatted(resultVariable)); + } + this.resultVariable = resultVariable; + } + + public DataVariable getResultVariable() { + return resultVariable; + } + + @Override + public Set getBoundVariables() { + return Set.of(resultVariable); + } + + @Override + protected Literal doSubstitute(Substitution substitution, List substitutedArguments) { + return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherCountLiteral = (CountLiteral) other; + return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + CountLiteral that = (CountLiteral) o; + return resultVariable.equals(that.resultVariable); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), resultVariable); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(resultVariable); + builder.append(" is count "); + builder.append(getTarget().toReferenceString()); + builder.append("("); + var argumentIterator = getArguments().iterator(); + if (argumentIterator.hasNext()) { + builder.append(argumentIterator.next()); + while (argumentIterator.hasNext()) { + builder.append(", ").append(argumentIterator.next()); + } + } + builder.append(")"); + return builder.toString(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java deleted file mode 100644 index 27917265..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/DnfCallLiteral.java +++ /dev/null @@ -1,40 +0,0 @@ -package tools.refinery.store.query.literal; - -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.equality.LiteralEqualityHelper; -import tools.refinery.store.query.substitution.Substitution; - -import java.util.List; - -public final class DnfCallLiteral extends CallLiteral implements PolarLiteral { - public DnfCallLiteral(CallPolarity polarity, Dnf target, List arguments) { - super(polarity, target, arguments); - } - - @Override - public Class getTargetType() { - return Dnf.class; - } - - @Override - public DnfCallLiteral substitute(Substitution substitution) { - return new DnfCallLiteral(getPolarity(), getTarget(), substituteArguments(substitution)); - } - - @Override - public DnfCallLiteral negate() { - return new DnfCallLiteral(getPolarity().negate(), getTarget(), getArguments()); - } - - @Override - public LiteralReduction getReduction() { - var dnfReduction = getTarget().getReduction(); - return getPolarity().isPositive() ? dnfReduction : dnfReduction.negate(); - } - - @Override - protected boolean targetEquals(LiteralEqualityHelper helper, Dnf otherTarget) { - return helper.dnfEqual(getTarget(), otherTarget); - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java index 61c753c3..4dc86b98 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java @@ -1,17 +1,19 @@ package tools.refinery.store.query.literal; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.NodeVariable; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.equality.LiteralEqualityHelper; import tools.refinery.store.query.substitution.Substitution; import java.util.Set; -public record EquivalenceLiteral(boolean positive, Variable left, Variable right) - implements PolarLiteral { +public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right) + implements CanNegate { @Override - public void collectAllVariables(Set variables) { - variables.add(left); - variables.add(right); + public Set getBoundVariables() { + // If one side of a {@code positive} equivalence is bound, it may bind its other side, but we under-approximate + // this behavior by not binding any of the sides by default. + return Set.of(); } @Override @@ -21,7 +23,8 @@ public record EquivalenceLiteral(boolean positive, Variable left, Variable right @Override public EquivalenceLiteral substitute(Substitution substitution) { - return new EquivalenceLiteral(positive, substitution.getSubstitute(left), substitution.getSubstitute(right)); + return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left), + substitution.getTypeSafeSubstitute(right)); } @Override diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java index ddd91775..6347410e 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java @@ -1,13 +1,13 @@ package tools.refinery.store.query.literal; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.equality.LiteralEqualityHelper; import tools.refinery.store.query.substitution.Substitution; import java.util.Set; public interface Literal { - void collectAllVariables(Set variables); + Set getBoundVariables(); Literal substitute(Substitution substitution); diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java index 2c7e893f..89039352 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java @@ -1,11 +1,17 @@ package tools.refinery.store.query.literal; +import tools.refinery.store.query.term.Term; + public final class Literals { private Literals() { throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); } - public static > T not(PolarLiteral literal) { + public static > T not(CanNegate literal) { return literal.negate(); } + + public static AssumeLiteral assume(Term term) { + return new AssumeLiteral(term); + } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java deleted file mode 100644 index 32523675..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/PolarLiteral.java +++ /dev/null @@ -1,5 +0,0 @@ -package tools.refinery.store.query.literal; - -public interface PolarLiteral> extends Literal { - T negate(); -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java deleted file mode 100644 index fb8b3332..00000000 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/RelationViewLiteral.java +++ /dev/null @@ -1,35 +0,0 @@ -package tools.refinery.store.query.literal; - -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.substitution.Substitution; -import tools.refinery.store.query.view.AnyRelationView; - -import java.util.List; - -public final class RelationViewLiteral extends CallLiteral - implements PolarLiteral { - public RelationViewLiteral(CallPolarity polarity, AnyRelationView target, List arguments) { - super(polarity, target, arguments); - } - - @Override - public Class getTargetType() { - return AnyRelationView.class; - } - - @Override - protected String targetToString() { - var target = getTarget(); - return "@RelationView(\"%s\") %s".formatted(target.getViewName(), target.getSymbol().name()); - } - - @Override - public RelationViewLiteral substitute(Substitution substitution) { - return new RelationViewLiteral(getPolarity(), getTarget(), substituteArguments(substitution)); - } - - @Override - public RelationViewLiteral negate() { - return new RelationViewLiteral(getPolarity().negate(), getTarget(), getArguments()); - } -} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java new file mode 100644 index 00000000..f8064ca2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java @@ -0,0 +1,10 @@ +package tools.refinery.store.query.substitution; + +import tools.refinery.store.query.term.Variable; + +public record CompositeSubstitution(Substitution first, Substitution second) implements Substitution { + @Override + public Variable getSubstitute(Variable variable) { + return second.getSubstitute(first.getSubstitute(variable)); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java index ffc65047..c7754619 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java @@ -1,6 +1,6 @@ package tools.refinery.store.query.substitution; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import java.util.Map; diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java index 54d18a3f..7847e582 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java @@ -1,6 +1,6 @@ package tools.refinery.store.query.substitution; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import java.util.HashMap; import java.util.Map; @@ -10,10 +10,6 @@ public class RenewingSubstitution implements Substitution { @Override public Variable getSubstitute(Variable variable) { - return alreadyRenewed.computeIfAbsent(variable, RenewingSubstitution::renew); - } - - private static Variable renew(Variable variable) { - return variable.isExplicitlyNamed() ? new Variable(variable.getName()) : new Variable(); + return alreadyRenewed.computeIfAbsent(variable, Variable::renew); } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java index d33ad6fb..eed414d9 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java @@ -1,6 +1,6 @@ package tools.refinery.store.query.substitution; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; public enum StatelessSubstitution implements Substitution { FAILING { diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java index 9d086bf5..99f84b9e 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java @@ -1,8 +1,29 @@ package tools.refinery.store.query.substitution; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.AnyDataVariable; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.NodeVariable; +import tools.refinery.store.query.term.Variable; @FunctionalInterface public interface Substitution { Variable getSubstitute(Variable variable); + + default NodeVariable getTypeSafeSubstitute(NodeVariable variable) { + var substitute = getSubstitute(variable); + return substitute.asNodeVariable(); + } + + default AnyDataVariable getTypeSafeSubstitute(AnyDataVariable variable) { + return getTypeSafeSubstitute((DataVariable) variable); + } + + default DataVariable getTypeSafeSubstitute(DataVariable variable) { + var substitute = getSubstitute(variable); + return substitute.asDataVariable(variable.getType()); + } + + default Substitution andThen(Substitution second) { + return new CompositeSubstitution(this, second); + } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java index 26cf1a20..5d4654da 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java @@ -1,6 +1,8 @@ package tools.refinery.store.query.substitution; -import tools.refinery.store.query.Variable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.term.Variable; import java.util.Map; @@ -24,4 +26,8 @@ public final class Substitutions { public static Substitution renewing() { return new RenewingSubstitution(); } + + public static Substitution compose(@Nullable Substitution first, @NotNull Substitution second) { + return first == null ? second : first.andThen(second); + } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java new file mode 100644 index 00000000..47421a94 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java @@ -0,0 +1,13 @@ +package tools.refinery.store.query.term; + +import java.util.stream.Stream; + +public interface Aggregator { + Class getResultType(); + + Class getInputType(); + + R aggregateStream(Stream stream); + + R getEmptyResult(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java new file mode 100644 index 00000000..ecfefcf9 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java @@ -0,0 +1,33 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.equality.LiteralEqualityHelper; + +import java.util.Set; + +public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable { + protected AnyDataVariable(String name) { + super(name); + } + + @Override + public NodeVariable asNodeVariable() { + throw new IllegalStateException("%s is a data variable".formatted(this)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable); + } + + @Override + public Set getInputVariables() { + return Set.of(this); + } + + @Override + public abstract AnyDataVariable renew(@Nullable String name); + + @Override + public abstract AnyDataVariable renew(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java new file mode 100644 index 00000000..8f998d45 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; + +import java.util.Set; + +public sealed interface AnyTerm permits AnyDataVariable, Term { + Class getType(); + + AnyTerm substitute(Substitution substitution); + + boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other); + + Set getInputVariables(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java new file mode 100644 index 00000000..8706a046 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java @@ -0,0 +1,26 @@ +package tools.refinery.store.query.term; + +public enum ArithmeticBinaryOperator { + ADD("+", true), + SUB("-", true), + MUL("*", true), + DIV("/", true), + POW("**", true), + MIN("min", false), + MAX("max", false); + + private final String text; + private final boolean infix; + + ArithmeticBinaryOperator(String text, boolean infix) { + this.text = text; + this.infix = infix; + } + + public String formatString(String left, String right) { + if (infix) { + return "(%s) %s (%s)".formatted(left, text, right); + } + return "%s(%s, %s)".formatted(text, left, right); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java new file mode 100644 index 00000000..887a1e6e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java @@ -0,0 +1,56 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; + +import java.util.Objects; + +public abstract class ArithmeticBinaryTerm extends BinaryTerm { + private final ArithmeticBinaryOperator operator; + + protected ArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term left, Term right) { + super(left, right); + this.operator = operator; + } + + @Override + public Class getLeftType() { + return getType(); + } + + @Override + public Class getRightType() { + return getType(); + } + + public ArithmeticBinaryOperator getOperator() { + return operator; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherArithmeticBinaryTerm = (ArithmeticBinaryTerm) other; + return operator == otherArithmeticBinaryTerm.operator; + } + + @Override + public String toString() { + return operator.formatString(getLeft().toString(), getRight().toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + ArithmeticBinaryTerm that = (ArithmeticBinaryTerm) o; + return operator == that.operator; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), operator); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java new file mode 100644 index 00000000..6a7c25db --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.term; + +public enum ArithmeticUnaryOperator { + PLUS("+"), + MINUS("-"); + + private final String prefix; + + ArithmeticUnaryOperator(String prefix) { + this.prefix = prefix; + } + + public String formatString(String body) { + return "%s(%s)".formatted(prefix, body); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java new file mode 100644 index 00000000..b78239c7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java @@ -0,0 +1,51 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; + +import java.util.Objects; + +public abstract class ArithmeticUnaryTerm extends UnaryTerm { + private final ArithmeticUnaryOperator operator; + + protected ArithmeticUnaryTerm(ArithmeticUnaryOperator operator, Term body) { + super(body); + this.operator = operator; + } + + @Override + public Class getBodyType() { + return getType(); + } + + public ArithmeticUnaryOperator getOperator() { + return operator; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherArithmeticUnaryTerm = (ArithmeticUnaryTerm) other; + return operator == otherArithmeticUnaryTerm.operator; + } + + @Override + public String toString() { + return operator.formatString(getBody().toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + ArithmeticUnaryTerm that = (ArithmeticUnaryTerm) o; + return operator == that.operator; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), operator); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java new file mode 100644 index 00000000..465e690f --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java @@ -0,0 +1,8 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.literal.Literal; + +@FunctionalInterface +public interface AssignedValue { + Literal toLiteral(DataVariable targetVariable); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java new file mode 100644 index 00000000..34f48ccc --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java @@ -0,0 +1,93 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.valuation.Valuation; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public abstract class BinaryTerm implements Term { + private final Term left; + private final Term right; + + protected BinaryTerm(Term left, Term right) { + if (!left.getType().equals(getLeftType())) { + throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted(left, + getLeftType().getName(), left.getType().getName())); + } + if (!right.getType().equals(getRightType())) { + throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted(right, + getRightType().getName(), right.getType().getName())); + } + this.left = left; + this.right = right; + } + + public abstract Class getLeftType(); + + public abstract Class getRightType(); + + public Term getLeft() { + return left; + } + + public Term getRight() { + return right; + } + + @Override + public R evaluate(Valuation valuation) { + var leftValue = left.evaluate(valuation); + if (leftValue == null) { + return null; + } + var rightValue = right.evaluate(valuation); + if (rightValue == null) { + return null; + } + return doEvaluate(leftValue, rightValue); + } + + protected abstract R doEvaluate(T1 leftValue, T2 rightValue); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (getClass() != other.getClass()) { + return false; + } + var otherBinaryTerm = (BinaryTerm) other; + return left.equalsWithSubstitution(helper, otherBinaryTerm.left) && right.equalsWithSubstitution(helper, + otherBinaryTerm.right); + } + + @Override + public Term substitute(Substitution substitution) { + return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution)); + } + + public abstract Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight); + + @Override + public Set getInputVariables() { + var inputVariables = new HashSet<>(left.getInputVariables()); + inputVariables.addAll(right.getInputVariables()); + return Collections.unmodifiableSet(inputVariables); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BinaryTerm that = (BinaryTerm) o; + return left.equals(that.left) && right.equals(that.right); + } + + @Override + public int hashCode() { + return Objects.hash(getClass(), left, right); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java new file mode 100644 index 00000000..44dcce10 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java @@ -0,0 +1,20 @@ +package tools.refinery.store.query.term; + +public enum ComparisonOperator { + EQ("=="), + NOT_EQ("!="), + LESS("<"), + LESS_EQ("<="), + GREATER(">"), + GREATER_EQ(">="); + + private final String text; + + ComparisonOperator(String text) { + this.text = text; + } + + public String formatString(String left, String right) { + return "(%s) %s (%s)".formatted(left, text, right); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java new file mode 100644 index 00000000..320d42df --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java @@ -0,0 +1,63 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; + +import java.util.Objects; + +public abstract class ComparisonTerm extends BinaryTerm { + private final ComparisonOperator operator; + + protected ComparisonTerm(ComparisonOperator operator, Term left, Term right) { + super(left, right); + this.operator = operator; + } + + @Override + public Class getType() { + return Boolean.class; + } + + public abstract Class getOperandType(); + + @Override + public Class getLeftType() { + return getOperandType(); + } + + @Override + public Class getRightType() { + return getOperandType(); + } + + public ComparisonOperator getOperator() { + return operator; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherComparisonTerm = (ComparisonTerm) other; + return operator == otherComparisonTerm.operator; + } + + @Override + public String toString() { + return operator.formatString(getLeft().toString(), getRight().toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + ComparisonTerm that = (ComparisonTerm) o; + return operator == that.operator; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), operator); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java new file mode 100644 index 00000000..2185fe37 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java @@ -0,0 +1,52 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.valuation.Valuation; + +import java.util.Set; + +public record ConstantTerm(Class type, T value) implements Term { + public ConstantTerm { + if (value == null) { + throw new IllegalArgumentException("value should not be null"); + } + if (!type.isInstance(value)) { + throw new IllegalArgumentException("value %s is not an instance of %s".formatted(value, type.getName())); + } + } + + @Override + public Class getType() { + return type; + } + + public T getValue() { + return value; + } + + @Override + public T evaluate(Valuation valuation) { + return getValue(); + } + + @Override + public Term substitute(Substitution substitution) { + return this; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return equals(other); + } + + @Override + public Set getInputVariables() { + return Set.of(); + } + + @Override + public String toString() { + return getValue().toString(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java new file mode 100644 index 00000000..4fb44492 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java @@ -0,0 +1,29 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; + +public record DataSort(Class type) implements Sort { + public static final DataSort INT = new DataSort<>(Integer.class); + + public static final DataSort BOOL = new DataSort<>(Boolean.class); + + @Override + public boolean isInstance(Variable variable) { + return variable instanceof DataVariable dataVariable && type.equals(dataVariable.getType()); + } + + @Override + public DataVariable newInstance(@Nullable String name) { + return Variable.of(name, type); + } + + @Override + public DataVariable newInstance() { + return newInstance(null); + } + + @Override + public String toString() { + return type.getName(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java new file mode 100644 index 00000000..af070ca7 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java @@ -0,0 +1,87 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.valuation.Valuation; + +import java.util.Objects; + +public final class DataVariable extends AnyDataVariable implements Term { + private final Class type; + + DataVariable(String name, Class type) { + super(name); + this.type = type; + } + + @Override + public DataSort getSort() { + return new DataSort<>(getType()); + } + + @Override + public Class getType() { + return type; + } + + @Override + public DataVariable renew(@Nullable String name) { + return new DataVariable<>(name, type); + } + + @Override + public DataVariable renew() { + return renew(getExplicitName()); + } + + @Override + public NodeVariable asNodeVariable() { + throw new IllegalStateException("%s is a data variable".formatted(this)); + } + + @Override + public DataVariable asDataVariable(Class newType) { + if (!getType().equals(newType)) { + throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(), + getType().getName())); + } + @SuppressWarnings("unchecked") + var result = (DataVariable) this; + return result; + } + + @Override + public T evaluate(Valuation valuation) { + return valuation.getValue(this); + } + + @Override + public Term substitute(Substitution substitution) { + return substitution.getTypeSafeSubstitute(this); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + return other instanceof DataVariable dataVariable && helper.variableEqual(this, dataVariable); + } + + public Literal assign(AssignedValue value) { + return value.toLiteral(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DataVariable that = (DataVariable) o; + return type.equals(that.type); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), type); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java new file mode 100644 index 00000000..57ff597c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java @@ -0,0 +1,103 @@ +package tools.refinery.store.query.term; + +import java.util.Comparator; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +public class ExtremeValueAggregator implements StatefulAggregator { + private final Class type; + private final T emptyResult; + private final Comparator comparator; + + public ExtremeValueAggregator(Class type, T emptyResult) { + this(type, emptyResult, null); + } + + public ExtremeValueAggregator(Class type, T emptyResult, Comparator comparator) { + this.type = type; + this.emptyResult = emptyResult; + this.comparator = comparator; + } + + @Override + public Class getResultType() { + return getInputType(); + } + + @Override + public Class getInputType() { + return type; + } + + @Override + public StatefulAggregate createEmptyAggregate() { + return new Aggregate(); + } + + @Override + public T getEmptyResult() { + return emptyResult; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtremeValueAggregator that = (ExtremeValueAggregator) o; + return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator, + that.comparator); + } + + @Override + public int hashCode() { + return Objects.hash(type, emptyResult, comparator); + } + + private class Aggregate implements StatefulAggregate { + private final SortedMap values; + + private Aggregate() { + values = new TreeMap<>(comparator); + } + + private Aggregate(Aggregate other) { + values = new TreeMap<>(other.values); + } + + @Override + public void add(T value) { + values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); + } + + @Override + public void remove(T value) { + values.compute(value, (theValue, currentCount) -> { + if (currentCount == null || currentCount <= 0) { + throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue)); + } + return currentCount.equals(1) ? null : currentCount - 1; + }); + } + + @Override + public T getResult() { + return isEmpty() ? emptyResult : values.firstKey(); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public StatefulAggregate deepCopy() { + return new Aggregate(this); + } + + @Override + public boolean contains(T value) { + return StatefulAggregate.super.contains(value); + } + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java new file mode 100644 index 00000000..1a4b2d4b --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java @@ -0,0 +1,30 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; + +public final class NodeSort implements Sort { + public static final NodeSort INSTANCE = new NodeSort(); + + private NodeSort() { + } + + @Override + public boolean isInstance(Variable variable) { + return variable instanceof NodeVariable; + } + + @Override + public NodeVariable newInstance(@Nullable String name) { + return new NodeVariable(name); + } + + @Override + public NodeVariable newInstance() { + return newInstance(null); + } + + @Override + public String toString() { + return ""; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java new file mode 100644 index 00000000..7419aaad --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java @@ -0,0 +1,48 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.literal.ConstantLiteral; +import tools.refinery.store.query.literal.EquivalenceLiteral; + +public final class NodeVariable extends Variable { + NodeVariable(@Nullable String name) { + super(name); + } + + @Override + public NodeSort getSort() { + return NodeSort.INSTANCE; + } + + @Override + public NodeVariable renew(@Nullable String name) { + return Variable.of(name); + } + + @Override + public NodeVariable renew() { + return renew(getExplicitName()); + } + + @Override + public NodeVariable asNodeVariable() { + return this; + } + + @Override + public DataVariable asDataVariable(Class type) { + throw new IllegalStateException("%s is a node variable".formatted(this)); + } + + public ConstantLiteral isConstant(int value) { + return new ConstantLiteral(this, value); + } + + public EquivalenceLiteral isEquivalent(NodeVariable other) { + return new EquivalenceLiteral(true, this, other); + } + + public EquivalenceLiteral notEquivalent(NodeVariable other) { + return new EquivalenceLiteral(false, this, other); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java new file mode 100644 index 00000000..8faa9c75 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java @@ -0,0 +1,80 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.substitution.Substitutions; +import tools.refinery.store.query.valuation.Valuation; + +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public final class OpaqueTerm implements Term { + private final Class type; + private final Function evaluator; + private final Set variables; + private final Substitution substitution; + + public OpaqueTerm(Class type, Function evaluator, + Set variables) { + this(type, evaluator, variables, null); + } + + private OpaqueTerm(Class type, Function evaluator, + Set variables, Substitution substitution) { + this.type = type; + this.evaluator = evaluator; + this.variables = Set.copyOf(variables); + this.substitution = substitution; + } + + @Override + public Class getType() { + return type; + } + + @Override + public Set getInputVariables() { + return variables; + } + + @Override + public T evaluate(Valuation valuation) { + return evaluator.apply(valuation.substitute(substitution)); + } + + @Override + public Term substitute(Substitution newSubstitution) { + var substitutedVariables = variables.stream() + .map(newSubstitution::getTypeSafeSubstitute) + .collect(Collectors.toUnmodifiableSet()); + return new OpaqueTerm<>(type, evaluator, substitutedVariables, + Substitutions.compose(substitution, newSubstitution)); + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + // Cannot inspect the opaque evaluator for deep equality. + return equals(other); + } + + @Override + public String toString() { + return ""; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OpaqueTerm that = (OpaqueTerm) o; + return type.equals(that.type) && evaluator.equals(that.evaluator) && Objects.equals(substitution, + that.substitution); + } + + @Override + public int hashCode() { + return Objects.hash(type, evaluator, substitution); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java new file mode 100644 index 00000000..622bcfce --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; + +public sealed interface Sort permits DataSort, NodeSort { + boolean isInstance(Variable variable); + + Variable newInstance(@Nullable String name); + + Variable newInstance(); +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java new file mode 100644 index 00000000..7ce91305 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java @@ -0,0 +1,17 @@ +package tools.refinery.store.query.term; + +public interface StatefulAggregate { + void add(T value); + + void remove(T value); + + R getResult(); + + boolean isEmpty(); + + StatefulAggregate deepCopy(); + + default boolean contains(T value) { + throw new UnsupportedOperationException(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java new file mode 100644 index 00000000..c215a511 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.term; + +import java.util.stream.Stream; + +public interface StatefulAggregator extends Aggregator { + StatefulAggregate createEmptyAggregate(); + + @Override + default R aggregateStream(Stream stream) { + var accumulator = createEmptyAggregate(); + var iterator = stream.iterator(); + while (iterator.hasNext()) { + var value = iterator.next(); + accumulator.add(value); + } + return accumulator.getResult(); + } + + @Override + default R getEmptyResult() { + return createEmptyAggregate().getResult(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java new file mode 100644 index 00000000..74dbd335 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java @@ -0,0 +1,20 @@ +package tools.refinery.store.query.term; + +import java.util.stream.Stream; + +public interface StatelessAggregator extends Aggregator { + R add(R current, T value); + + R remove(R current, T value); + + @Override + default R aggregateStream(Stream stream) { + var accumulator = getEmptyResult(); + var iterator = stream.iterator(); + while (iterator.hasNext()) { + var value = iterator.next(); + accumulator = add(accumulator, value); + } + return accumulator; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java new file mode 100644 index 00000000..95434db2 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java @@ -0,0 +1,21 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.literal.AssignLiteral; +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.valuation.Valuation; + +public non-sealed interface Term extends AnyTerm, AssignedValue { + @Override + Class getType(); + + T evaluate(Valuation valuation); + + @Override + Term substitute(Substitution substitution); + + @Override + default Literal toLiteral(DataVariable targetVariable) { + return new AssignLiteral<>(targetVariable, this); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java new file mode 100644 index 00000000..4083111a --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java @@ -0,0 +1,68 @@ +package tools.refinery.store.query.term; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.valuation.Valuation; + +import java.util.Objects; +import java.util.Set; + +public abstract class UnaryTerm implements Term { + private final Term body; + + protected UnaryTerm(Term body) { + if (!body.getType().equals(getBodyType())) { + throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body, + getBodyType().getName(), body.getType().getName())); + } + this.body = body; + } + + public abstract Class getBodyType(); + + public Term getBody() { + return body; + } + + @Override + public R evaluate(Valuation valuation) { + var bodyValue = body.evaluate(valuation); + return bodyValue == null ? null : doEvaluate(bodyValue); + } + + protected abstract R doEvaluate(T bodyValue); + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (getClass() != other.getClass()) { + return false; + } + var otherUnaryTerm = (UnaryTerm) other; + return body.equalsWithSubstitution(helper, otherUnaryTerm.body); + } + + @Override + public Term substitute(Substitution substitution) { + return doSubstitute(substitution, body.substitute(substitution)); + } + + protected abstract Term doSubstitute(Substitution substitution, Term substitutedBody); + + @Override + public Set getInputVariables() { + return body.getInputVariables(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UnaryTerm unaryTerm = (UnaryTerm) o; + return body.equals(unaryTerm.body); + } + + @Override + public int hashCode() { + return Objects.hash(getClass(), body); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java new file mode 100644 index 00000000..957e10f8 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java @@ -0,0 +1,76 @@ +package tools.refinery.store.query.term; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.dnf.DnfUtils; + +import java.util.Objects; + +public abstract sealed class Variable permits AnyDataVariable, NodeVariable { + private final String explicitName; + private final String uniqueName; + + protected Variable(String name) { + this.explicitName = name; + uniqueName = DnfUtils.generateUniqueName(name); + } + + public abstract Sort getSort(); + + public String getName() { + return explicitName == null ? uniqueName : explicitName; + } + + protected String getExplicitName() { + return explicitName; + } + + public boolean isExplicitlyNamed() { + return explicitName != null; + } + + public String getUniqueName() { + return uniqueName; + } + + public abstract Variable renew(@Nullable String name); + + public abstract Variable renew(); + + public abstract NodeVariable asNodeVariable(); + + public abstract DataVariable asDataVariable(Class type); + + @Override + public String toString() { + return getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Variable variable = (Variable) o; + return Objects.equals(uniqueName, variable.uniqueName); + } + + @Override + public int hashCode() { + return Objects.hash(uniqueName); + } + + public static NodeVariable of(@Nullable String name) { + return new NodeVariable(name); + } + + public static NodeVariable of() { + return of((String) null); + } + + public static DataVariable of(@Nullable String name, Class type) { + return new DataVariable<>(name, type); + } + + public static DataVariable of(Class type) { + return of(null, type); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java new file mode 100644 index 00000000..5079f1ce --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.term.bool; + +import tools.refinery.store.query.term.ConstantTerm; + +public final class BoolConstantTerm { + public static final ConstantTerm TRUE = new ConstantTerm<>(Boolean.class, true); + public static final ConstantTerm FALSE = new ConstantTerm<>(Boolean.class, false); + + private BoolConstantTerm() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } + + public static ConstantTerm valueOf(boolean boolValue) { + return boolValue ? TRUE : FALSE; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java new file mode 100644 index 00000000..d85f864d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java @@ -0,0 +1,78 @@ +package tools.refinery.store.query.term.bool; + +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.*; + +import java.util.Objects; + +public class BoolLogicBinaryTerm extends BinaryTerm { + private final LogicBinaryOperator operator; + + protected BoolLogicBinaryTerm(LogicBinaryOperator operator, Term left, Term right) { + super(left, right); + this.operator = operator; + } + + @Override + public Class getType() { + return Boolean.class; + } + + @Override + public Class getLeftType() { + return getType(); + } + + @Override + public Class getRightType() { + return getType(); + } + + public LogicBinaryOperator getOperator() { + return operator; + } + + @Override + public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) { + if (!super.equalsWithSubstitution(helper, other)) { + return false; + } + var otherBoolLogicBinaryTerm = (BoolLogicBinaryTerm) other; + return operator == otherBoolLogicBinaryTerm.operator; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new BoolLogicBinaryTerm(getOperator(), substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) { + return switch (getOperator()) { + case AND -> leftValue && rightValue; + case OR -> leftValue || rightValue; + case XOR -> leftValue ^ rightValue; + }; + } + + @Override + public String toString() { + return operator.formatString(getLeft().toString(), getRight().toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + BoolLogicBinaryTerm that = (BoolLogicBinaryTerm) o; + return operator == that.operator; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), operator); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java new file mode 100644 index 00000000..855139b5 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java @@ -0,0 +1,36 @@ +package tools.refinery.store.query.term.bool; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.UnaryTerm; + +public class BoolNotTerm extends UnaryTerm { + protected BoolNotTerm(Term body) { + super(body); + } + + @Override + public Class getType() { + return Boolean.class; + } + + @Override + public Class getBodyType() { + return getType(); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new BoolNotTerm(substitutedBody); + } + + @Override + protected Boolean doEvaluate(Boolean bodyValue) { + return !bodyValue; + } + + @Override + public String toString() { + return "!(%s)".formatted(getBody()); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java new file mode 100644 index 00000000..3d6c8d9d --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java @@ -0,0 +1,30 @@ +package tools.refinery.store.query.term.bool; + +import tools.refinery.store.query.term.ConstantTerm; +import tools.refinery.store.query.term.Term; + +public final class BoolTerms { + private BoolTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static ConstantTerm constant(boolean value) { + return BoolConstantTerm.valueOf(value); + } + + public static BoolNotTerm not(Term body) { + return new BoolNotTerm(body); + } + + public static BoolLogicBinaryTerm and(Term left, Term right) { + return new BoolLogicBinaryTerm(LogicBinaryOperator.AND, left, right); + } + + public static BoolLogicBinaryTerm or(Term left, Term right) { + return new BoolLogicBinaryTerm(LogicBinaryOperator.OR, left, right); + } + + public static BoolLogicBinaryTerm xor(Term left, Term right) { + return new BoolLogicBinaryTerm(LogicBinaryOperator.XOR, left, right); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java new file mode 100644 index 00000000..ca9ac66e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java @@ -0,0 +1,17 @@ +package tools.refinery.store.query.term.bool; + +public enum LogicBinaryOperator { + AND("&&"), + OR("||"), + XOR("^^"); + + private final String text; + + LogicBinaryOperator(String text) { + this.text = text; + } + + public String formatString(String left, String right) { + return "(%s) %s (%s)".formatted(left, text, right); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java new file mode 100644 index 00000000..32e41718 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java @@ -0,0 +1,48 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.ArithmeticBinaryOperator; +import tools.refinery.store.query.term.ArithmeticBinaryTerm; +import tools.refinery.store.query.term.Term; + +public class IntArithmeticBinaryTerm extends ArithmeticBinaryTerm { + public IntArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term left, Term right) { + super(operator, left, right); + } + + @Override + public Class getType() { + return Integer.class; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight); + } + + @Override + protected Integer doEvaluate(Integer leftValue, Integer rightValue) { + return switch (getOperator()) { + case ADD -> leftValue + rightValue; + case SUB -> leftValue - rightValue; + case MUL -> leftValue * rightValue; + case DIV -> rightValue == 0 ? null : leftValue / rightValue; + case POW -> rightValue < 0 ? null : power(leftValue, rightValue); + case MIN -> Math.min(leftValue, rightValue); + case MAX -> Math.max(leftValue, rightValue); + }; + } + + private static int power(int base, int exponent) { + int accum = 1; + while (exponent > 0) { + if (exponent % 2 == 1) { + accum = accum * base; + } + base = base * base; + exponent = exponent / 2; + } + return accum; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java new file mode 100644 index 00000000..1e769259 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java @@ -0,0 +1,30 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.ArithmeticUnaryOperator; +import tools.refinery.store.query.term.ArithmeticUnaryTerm; + +public class IntArithmeticUnaryTerm extends ArithmeticUnaryTerm { + public IntArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term body) { + super(operation, body); + } + + @Override + public Class getType() { + return Integer.class; + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new IntArithmeticUnaryTerm(getOperator(), substitutedBody); + } + + @Override + protected Integer doEvaluate(Integer bodyValue) { + return switch(getOperator()) { + case PLUS -> bodyValue; + case MINUS -> -bodyValue; + }; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java new file mode 100644 index 00000000..322d2b80 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java @@ -0,0 +1,34 @@ +package tools.refinery.store.query.term.int_; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.ComparisonOperator; +import tools.refinery.store.query.term.ComparisonTerm; +import tools.refinery.store.query.term.Term; + +public class IntComparisonTerm extends ComparisonTerm { + public IntComparisonTerm(ComparisonOperator operator, Term left, Term right) { + super(operator, left, right); + } + + @Override + public Class getOperandType() { + return Integer.class; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new IntComparisonTerm(getOperator(), substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Integer leftValue, Integer rightValue) { + return switch (getOperator()) { + case EQ -> leftValue.equals(rightValue); + case NOT_EQ -> !leftValue.equals(rightValue); + case LESS -> leftValue < rightValue; + case LESS_EQ -> leftValue <= rightValue; + case GREATER -> leftValue > rightValue; + case GREATER_EQ -> leftValue >= rightValue; + }; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java new file mode 100644 index 00000000..d5a6add0 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java @@ -0,0 +1,17 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.term.ExtremeValueAggregator; + +import java.util.Comparator; + +public final class IntExtremeValueAggregator { + public static final ExtremeValueAggregator MINIMUM = new ExtremeValueAggregator<>(Integer.class, + Integer.MAX_VALUE); + + public static final ExtremeValueAggregator MAXIMUM = new ExtremeValueAggregator<>(Integer.class, + Integer.MIN_VALUE, Comparator.reverseOrder()); + + private IntExtremeValueAggregator() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java new file mode 100644 index 00000000..65024f52 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java @@ -0,0 +1,35 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.term.StatelessAggregator; + +public final class IntSumAggregator implements StatelessAggregator { + public static final IntSumAggregator INSTANCE = new IntSumAggregator(); + + private IntSumAggregator() { + } + + @Override + public Class getResultType() { + return Integer.class; + } + + @Override + public Class getInputType() { + return Integer.class; + } + + @Override + public Integer getEmptyResult() { + return 0; + } + + @Override + public Integer add(Integer current, Integer value) { + return current + value; + } + + @Override + public Integer remove(Integer current, Integer value) { + return current - value; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java new file mode 100644 index 00000000..86594deb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java @@ -0,0 +1,81 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.term.*; + +public final class IntTerms { + public static final Aggregator INT_SUM = IntSumAggregator.INSTANCE; + public static final Aggregator INT_MIN = IntExtremeValueAggregator.MINIMUM; + public static final Aggregator INT_MAX = IntExtremeValueAggregator.MAXIMUM; + + private IntTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static ConstantTerm constant(int value) { + return new ConstantTerm<>(Integer.class, value); + } + + public static IntArithmeticUnaryTerm plus(Term body) { + return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body); + } + + public static IntArithmeticUnaryTerm minus(Term body) { + return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body); + } + + public static IntArithmeticBinaryTerm add(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right); + } + + public static IntArithmeticBinaryTerm sub(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right); + } + + public static IntArithmeticBinaryTerm mul(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right); + } + + public static IntArithmeticBinaryTerm div(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right); + } + + public static IntArithmeticBinaryTerm pow(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right); + } + + public static IntArithmeticBinaryTerm min(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right); + } + + public static IntArithmeticBinaryTerm max(Term left, Term right) { + return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right); + } + + public static IntComparisonTerm eq(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.EQ, left, right); + } + + public static IntComparisonTerm notEq(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.NOT_EQ, left, right); + } + + public static IntComparisonTerm less(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.LESS, left, right); + } + + public static IntComparisonTerm lessEq(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.LESS_EQ, left, right); + } + + public static IntComparisonTerm greater(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.GREATER, left, right); + } + + public static IntComparisonTerm greaterEq(Term left, Term right) { + return new IntComparisonTerm(ComparisonOperator.GREATER_EQ, left, right); + } + + public static RealToIntTerm asInt(Term body) { + return new RealToIntTerm(body); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java new file mode 100644 index 00000000..53875ddc --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java @@ -0,0 +1,36 @@ +package tools.refinery.store.query.term.int_; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.UnaryTerm; + +public class RealToIntTerm extends UnaryTerm { + protected RealToIntTerm(Term body) { + super(body); + } + + @Override + public Class getType() { + return Integer.class; + } + + @Override + public Class getBodyType() { + return Double.class; + } + + @Override + protected Integer doEvaluate(Double bodyValue) { + return bodyValue.intValue(); + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new RealToIntTerm(substitutedBody); + } + + @Override + public String toString() { + return "(%s) as int".formatted(getBody()); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java new file mode 100644 index 00000000..55590824 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java @@ -0,0 +1,36 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.Term; +import tools.refinery.store.query.term.UnaryTerm; + +public class IntToRealTerm extends UnaryTerm { + protected IntToRealTerm(Term body) { + super(body); + } + + @Override + public Class getType() { + return Double.class; + } + + @Override + public Class getBodyType() { + return Integer.class; + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new IntToRealTerm(substitutedBody); + } + + @Override + protected Double doEvaluate(Integer bodyValue) { + return bodyValue.doubleValue(); + } + + @Override + public String toString() { + return "(%s) as real".formatted(getBody()); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java new file mode 100644 index 00000000..57bcbe5e --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java @@ -0,0 +1,36 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.ArithmeticBinaryOperator; +import tools.refinery.store.query.term.ArithmeticBinaryTerm; +import tools.refinery.store.query.term.Term; + +public class RealArithmeticBinaryTerm extends ArithmeticBinaryTerm { + public RealArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term left, Term right) { + super(operator, left, right); + } + + @Override + public Class getType() { + return Double.class; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight); + } + + @Override + protected Double doEvaluate(Double leftValue, Double rightValue) { + return switch (getOperator()) { + case ADD -> leftValue + rightValue; + case SUB -> leftValue - rightValue; + case MUL -> leftValue * rightValue; + case DIV -> leftValue / rightValue; + case POW -> Math.pow(leftValue, rightValue); + case MIN -> Math.min(leftValue, rightValue); + case MAX -> Math.max(leftValue, rightValue); + }; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java new file mode 100644 index 00000000..632e68bf --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java @@ -0,0 +1,30 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.ArithmeticUnaryOperator; +import tools.refinery.store.query.term.ArithmeticUnaryTerm; +import tools.refinery.store.query.term.Term; + +public class RealArithmeticUnaryTerm extends ArithmeticUnaryTerm { + public RealArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term body) { + super(operation, body); + } + + @Override + public Class getType() { + return Double.class; + } + + @Override + protected Term doSubstitute(Substitution substitution, Term substitutedBody) { + return new RealArithmeticUnaryTerm(getOperator(), substitutedBody); + } + + @Override + protected Double doEvaluate(Double bodyValue) { + return switch(getOperator()) { + case PLUS -> bodyValue; + case MINUS -> -bodyValue; + }; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java new file mode 100644 index 00000000..75d97adb --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java @@ -0,0 +1,35 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.ComparisonOperator; +import tools.refinery.store.query.term.ComparisonTerm; +import tools.refinery.store.query.term.Term; + +public class RealComparisonTerm extends ComparisonTerm { + public RealComparisonTerm(ComparisonOperator operator, Term left, Term right) { + super(operator, left, right); + } + + @Override + public Class getOperandType() { + return Double.class; + } + + @Override + public Term doSubstitute(Substitution substitution, Term substitutedLeft, + Term substitutedRight) { + return new RealComparisonTerm(getOperator(), substitutedLeft, substitutedRight); + } + + @Override + protected Boolean doEvaluate(Double leftValue, Double rightValue) { + return switch (getOperator()) { + case EQ -> leftValue.equals(rightValue); + case NOT_EQ -> !leftValue.equals(rightValue); + case LESS -> leftValue < rightValue; + case LESS_EQ -> leftValue <= rightValue; + case GREATER -> leftValue > rightValue; + case GREATER_EQ -> leftValue >= rightValue; + }; + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java new file mode 100644 index 00000000..23384530 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java @@ -0,0 +1,17 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.term.ExtremeValueAggregator; + +import java.util.Comparator; + +public final class RealExtremeValueAggregator { + public static final ExtremeValueAggregator MINIMUM = new ExtremeValueAggregator<>(Double.class, + Double.POSITIVE_INFINITY); + + public static final ExtremeValueAggregator MAXIMUM = new ExtremeValueAggregator<>(Double.class, + Double.NEGATIVE_INFINITY, Comparator.reverseOrder()); + + private RealExtremeValueAggregator() { + throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java new file mode 100644 index 00000000..d5888664 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java @@ -0,0 +1,85 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.term.StatefulAggregate; +import tools.refinery.store.query.term.StatefulAggregator; + +import java.util.Map; +import java.util.TreeMap; + +public final class RealSumAggregator implements StatefulAggregator { + public static final RealSumAggregator INSTANCE = new RealSumAggregator(); + + private RealSumAggregator() { + } + + @Override + public Class getResultType() { + return null; + } + + @Override + public Class getInputType() { + return null; + } + + @Override + public StatefulAggregate createEmptyAggregate() { + return new Aggregate(); + } + + @Override + public Double getEmptyResult() { + return 0d; + } + + private static class Aggregate implements StatefulAggregate { + private final Map values; + + public Aggregate() { + values = new TreeMap<>(); + } + + private Aggregate(Aggregate other) { + values = new TreeMap<>(other.values); + } + + @Override + public void add(Double value) { + values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1); + } + + @Override + public void remove(Double value) { + values.compute(value, (theValue, currentCount) -> { + if (currentCount == null || currentCount <= 0) { + throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue)); + } + return currentCount.equals(1) ? null : currentCount - 1; + }); + } + + @Override + public Double getResult() { + return values.entrySet() + .stream() + .mapToDouble(entry -> entry.getKey() * entry.getValue()) + .reduce(Double::sum) + .orElse(0d); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public StatefulAggregate deepCopy() { + return new Aggregate(this); + } + + @Override + public boolean contains(Double value) { + return values.containsKey(value); + } + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java new file mode 100644 index 00000000..a8117842 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java @@ -0,0 +1,81 @@ +package tools.refinery.store.query.term.real; + +import tools.refinery.store.query.term.*; + +public final class RealTerms { + public static final Aggregator REAL_SUM = RealSumAggregator.INSTANCE; + public static final Aggregator REAL_MIN = RealExtremeValueAggregator.MINIMUM; + public static final Aggregator REAL_MAX = RealExtremeValueAggregator.MAXIMUM; + + private RealTerms() { + throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly"); + } + + public static ConstantTerm constant(double value) { + return new ConstantTerm<>(Double.class, value); + } + + public static RealArithmeticUnaryTerm plus(Term body) { + return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body); + } + + public static RealArithmeticUnaryTerm minus(Term body) { + return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body); + } + + public static RealArithmeticBinaryTerm add(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right); + } + + public static RealArithmeticBinaryTerm sub(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right); + } + + public static RealArithmeticBinaryTerm mul(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right); + } + + public static RealArithmeticBinaryTerm div(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right); + } + + public static RealArithmeticBinaryTerm pow(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right); + } + + public static RealArithmeticBinaryTerm min(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right); + } + + public static RealArithmeticBinaryTerm max(Term left, Term right) { + return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right); + } + + public static RealComparisonTerm eq(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.EQ, left, right); + } + + public static RealComparisonTerm notEq(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.NOT_EQ, left, right); + } + + public static RealComparisonTerm less(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.LESS, left, right); + } + + public static RealComparisonTerm lessEq(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.LESS_EQ, left, right); + } + + public static RealComparisonTerm greater(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.GREATER, left, right); + } + + public static RealComparisonTerm greaterEq(Term left, Term right) { + return new RealComparisonTerm(ComparisonOperator.GREATER_EQ, left, right); + } + + public static IntToRealTerm asReal(Term body) { + return new IntToRealTerm(body); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java new file mode 100644 index 00000000..fb512d88 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.valuation; + +import tools.refinery.store.query.term.AnyDataVariable; +import tools.refinery.store.query.term.DataVariable; + +import java.util.Set; + +public record RestrictedValuation(Valuation valuation, Set allowedVariables) implements Valuation { + @Override + public T getValue(DataVariable variable) { + if (!allowedVariables.contains(variable)) { + throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable)); + } + return valuation.getValue(variable); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java new file mode 100644 index 00000000..8e79663c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java @@ -0,0 +1,11 @@ +package tools.refinery.store.query.valuation; + +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.DataVariable; + +public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation { + @Override + public T getValue(DataVariable variable) { + return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable)); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java new file mode 100644 index 00000000..3ba9a6b8 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java @@ -0,0 +1,23 @@ +package tools.refinery.store.query.valuation; + +import org.jetbrains.annotations.Nullable; +import tools.refinery.store.query.substitution.Substitution; +import tools.refinery.store.query.term.AnyDataVariable; +import tools.refinery.store.query.term.DataVariable; + +import java.util.Set; + +public interface Valuation { + T getValue(DataVariable variable); + + default Valuation substitute(@Nullable Substitution substitution) { + if (substitution == null) { + return this; + } + return new SubstitutedValuation(this, substitution); + } + + default Valuation restrict(Set allowedVariables) { + return new RestrictedValuation(this, Set.copyOf(allowedVariables)); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java index bc3ac1ea..6ae410f2 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java @@ -1,13 +1,13 @@ package tools.refinery.store.query.view; import tools.refinery.store.model.Model; -import tools.refinery.store.query.FunctionalDependency; +import tools.refinery.store.query.dnf.FunctionalDependency; import tools.refinery.store.representation.AnySymbol; -import tools.refinery.store.query.RelationLike; +import tools.refinery.store.query.Constraint; import java.util.Set; -public sealed interface AnyRelationView extends RelationLike permits RelationView { +public sealed interface AnyRelationView extends Constraint permits RelationView { AnySymbol getSymbol(); String getViewName(); diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java new file mode 100644 index 00000000..050b9496 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.view; + +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +public class ForbiddenRelationView extends TuplePreservingRelationView { + public ForbiddenRelationView(Symbol symbol) { + super(symbol, "forbidden"); + } + + @Override + public boolean filter(Tuple key, TruthValue value) { + return !value.may(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java index 3d278a8b..7ec9e7ac 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java @@ -1,22 +1,31 @@ package tools.refinery.store.query.view; import tools.refinery.store.model.Model; -import tools.refinery.store.query.FunctionalDependency; +import tools.refinery.store.query.dnf.FunctionalDependency; +import tools.refinery.store.query.term.DataSort; +import tools.refinery.store.query.term.NodeSort; +import tools.refinery.store.query.term.Sort; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple1; +import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; public final class FunctionalRelationView extends RelationView { + private final T defaultValue; + public FunctionalRelationView(Symbol symbol, String name) { super(symbol, name); + defaultValue = symbol.defaultValue(); } public FunctionalRelationView(Symbol symbol) { super(symbol); + defaultValue = symbol.defaultValue(); } @Override @@ -37,7 +46,7 @@ public final class FunctionalRelationView extends RelationView { @Override public boolean filter(Tuple key, T value) { - return true; + return !Objects.equals(defaultValue, value); } @Override @@ -68,4 +77,29 @@ public final class FunctionalRelationView extends RelationView { public int arity() { return getSymbol().arity() + 1; } + + @Override + public List getSorts() { + var sorts = new Sort[arity()]; + int valueIndex = sorts.length - 1; + for (int i = 0; i < valueIndex; i++) { + sorts[i] = NodeSort.INSTANCE; + } + sorts[valueIndex] = new DataSort<>(getSymbol().valueType()); + return List.of(sorts); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + FunctionalRelationView that = (FunctionalRelationView) o; + return Objects.equals(defaultValue, that.defaultValue); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), defaultValue); + } } diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java new file mode 100644 index 00000000..a2a84b3c --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.view; + +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +public class MayRelationView extends TuplePreservingRelationView { + public MayRelationView(Symbol symbol) { + super(symbol, "may"); + } + + @Override + public boolean filter(Tuple key, TruthValue value) { + return value.may(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java new file mode 100644 index 00000000..72ac0ca3 --- /dev/null +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java @@ -0,0 +1,16 @@ +package tools.refinery.store.query.view; + +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +public class MustRelationView extends TuplePreservingRelationView { + public MustRelationView(Symbol symbol) { + super(symbol, "must"); + } + + @Override + public boolean filter(Tuple key, TruthValue value) { + return value.must(); + } +} diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java index ea9fd5e2..d7164b3b 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java @@ -1,14 +1,10 @@ package tools.refinery.store.query.view; -import tools.refinery.store.query.Variable; import tools.refinery.store.map.CursorAsIterator; import tools.refinery.store.model.Model; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.RelationViewLiteral; import tools.refinery.store.representation.Symbol; import tools.refinery.store.tuple.Tuple; -import java.util.List; import java.util.Objects; import java.util.UUID; @@ -56,20 +52,14 @@ public abstract non-sealed class RelationView implements AnyRelationView { return (() -> new CursorAsIterator<>(model.getInterpretation(symbol).getAll(), this::forwardMap, this::filter)); } - public RelationViewLiteral call(CallPolarity polarity, List arguments) { - return new RelationViewLiteral(polarity, this, arguments); - } - - public RelationViewLiteral call(CallPolarity polarity, Variable... arguments) { - return call(polarity, List.of(arguments)); - } - - public RelationViewLiteral call(Variable... arguments) { - return call(CallPolarity.POSITIVE, arguments); + @Override + public String toString() { + return name(); } - public RelationViewLiteral callTransitive(Variable left, Variable right) { - return call(CallPolarity.TRANSITIVE, List.of(left, right)); + @Override + public String toReferenceString() { + return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name()); } @Override diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java index 8cc4986e..234b3a9a 100644 --- a/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java +++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java @@ -1,10 +1,15 @@ package tools.refinery.store.query.view; import tools.refinery.store.model.Model; +import tools.refinery.store.query.term.NodeSort; +import tools.refinery.store.query.term.Sort; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.Tuple1; import tools.refinery.store.representation.Symbol; +import java.util.Arrays; +import java.util.List; + public abstract class TuplePreservingRelationView extends RelationView { protected TuplePreservingRelationView(Symbol symbol, String name) { super(symbol, name); @@ -38,7 +43,15 @@ public abstract class TuplePreservingRelationView extends RelationView { return filter(key, value); } + @Override public int arity() { return this.getSymbol().arity(); } + + @Override + public List getSorts() { + var sorts = new Sort[arity()]; + Arrays.fill(sorts, NodeSort.INSTANCE); + return List.of(sorts); + } } diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java index e6701fe3..ceb46d6f 100644 --- a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java @@ -1,7 +1,9 @@ package tools.refinery.store.query; import org.junit.jupiter.api.Test; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.literal.BooleanLiteral; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; @@ -12,8 +14,8 @@ import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo class DnfBuilderTest { @Test void eliminateTrueTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); @@ -28,8 +30,8 @@ class DnfBuilderTest { @Test void eliminateFalseTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); @@ -45,8 +47,8 @@ class DnfBuilderTest { @Test void alwaysTrueTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); @@ -62,8 +64,8 @@ class DnfBuilderTest { @Test void alwaysFalseTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); @@ -78,8 +80,8 @@ class DnfBuilderTest { @Test void eliminateTrueDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var trueDnf = Dnf.builder().parameter(p).clause().build(); @@ -95,8 +97,8 @@ class DnfBuilderTest { @Test void eliminateFalseDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var falseDnf = Dnf.builder().parameter(p).build(); @@ -113,8 +115,8 @@ class DnfBuilderTest { @Test void alwaysTrueDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var trueDnf = Dnf.builder().parameter(p).clause().build(); @@ -131,8 +133,8 @@ class DnfBuilderTest { @Test void alwaysFalseDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var falseDnf = Dnf.builder().parameter(p).build(); @@ -148,8 +150,8 @@ class DnfBuilderTest { @Test void eliminateNotFalseDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var falseDnf = Dnf.builder().parameter(p).build(); @@ -165,8 +167,8 @@ class DnfBuilderTest { @Test void eliminateNotTrueDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var trueDnf = Dnf.builder().parameter(p).clause().build(); @@ -183,8 +185,8 @@ class DnfBuilderTest { @Test void alwaysNotFalseDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var falseDnf = Dnf.builder().parameter(p).build(); @@ -201,8 +203,8 @@ class DnfBuilderTest { @Test void alwaysNotTrueDnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var friend = new Symbol<>("friend", 2, Boolean.class, false); var friendView = new KeyOnlyRelationView<>(friend); var trueDnf = Dnf.builder().parameter(p).clause().build(); diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java new file mode 100644 index 00000000..9b469bb0 --- /dev/null +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java @@ -0,0 +1,174 @@ +package tools.refinery.store.query; + +import org.junit.jupiter.api.Test; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.view.KeyOnlyRelationView; +import tools.refinery.store.representation.Symbol; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static tools.refinery.store.query.literal.Literals.not; + +class DnfToDefinitionStringTest { + @Test + void noClausesTest() { + var p = Variable.of("p"); + var dnf = Dnf.builder("Example").parameter(p).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + . + """)); + } + + @Test + void noParametersTest() { + var dnf = Dnf.builder("Example").build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example() <-> + . + """)); + } + + @Test + void emptyClauseTest() { + var p = Variable.of("p"); + var dnf = Dnf.builder("Example").parameter(p).clause().build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + . + """)); + } + + @Test + void relationViewPositiveTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + @RelationView("key") friend(p, q). + """)); + } + + @Test + void relationViewNegativeTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example").parameter(p).clause(not(friendView.call(p, q))).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + !(@RelationView("key") friend(p, q)). + """)); + } + + @Test + void relationViewTransitiveTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + @RelationView("key") friend+(p, q). + """)); + } + + @Test + void multipleParametersTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p, q) <-> + @RelationView("key") friend(p, q). + """)); + } + + @Test + void multipleLiteralsTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var person = new Symbol<>("person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example") + .parameter(p) + .clause( + personView.call(p), + personView.call(q), + friendView.call(p, q) + ) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + @RelationView("key") person(p), + @RelationView("key") person(q), + @RelationView("key") friend(p, q). + """)); + } + + @Test + void multipleClausesTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var dnf = Dnf.builder("Example") + .parameter(p) + .clause(friendView.call(p, q)) + .clause(friendView.call(q, p)) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + @RelationView("key") friend(p, q) + ; + @RelationView("key") friend(q, p). + """)); + } + + @Test + void dnfTest() { + var p = Variable.of("p"); + var q = Variable.of("q"); + var r = Variable.of("r"); + var s = Variable.of("s"); + var person = new Symbol<>("person", 1, Boolean.class, false); + var personView = new KeyOnlyRelationView<>(person); + var friend = new Symbol<>("friend", 2, Boolean.class, false); + var friendView = new KeyOnlyRelationView<>(friend); + var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build(); + var dnf = Dnf.builder("Example") + .parameter(p) + .clause( + personView.call(p), + personView.call(q), + not(called.call(p, q)) + ) + .build(); + + assertThat(dnf.toDefinitionString(), is(""" + pred Example(p) <-> + @RelationView("key") person(p), + @RelationView("key") person(q), + !(@Dnf Called(p, q)). + """)); + } +} diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToStringTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToStringTest.java deleted file mode 100644 index e6e4bef3..00000000 --- a/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToStringTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package tools.refinery.store.query; - -import org.junit.jupiter.api.Test; -import tools.refinery.store.query.view.KeyOnlyRelationView; -import tools.refinery.store.representation.Symbol; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static tools.refinery.store.query.literal.Literals.not; - -class DnfToStringTest { - @Test - void noClausesTest() { - var p = new Variable("p"); - var dnf = Dnf.builder("Example").parameter(p).build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - . - """)); - } - - @Test - void noParametersTest() { - var dnf = Dnf.builder("Example").build(); - - assertThat(dnf.toString(), is(""" - pred Example() <-> - . - """)); - } - - @Test - void emptyClauseTest() { - var p = new Variable("p"); - var dnf = Dnf.builder("Example").parameter(p).clause().build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - . - """)); - } - - @Test - void relationViewPositiveTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - @RelationView("key") friend(p, q). - """)); - } - - @Test - void relationViewNegativeTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example").parameter(p).clause(not(friendView.call(p, q))).build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - !(@RelationView("key") friend(p, q)). - """)); - } - - @Test - void relationViewTransitiveTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - @RelationView("key") friend+(p, q). - """)); - } - - @Test - void multipleParametersTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build(); - - assertThat(dnf.toString(), is(""" - pred Example(p, q) <-> - @RelationView("key") friend(p, q). - """)); - } - - @Test - void multipleLiteralsTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var person = new Symbol<>("person", 1, Boolean.class, false); - var personView = new KeyOnlyRelationView<>(person); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example") - .parameter(p) - .clause( - personView.call(p), - personView.call(q), - friendView.call(p, q) - ) - .build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - @RelationView("key") person(p), - @RelationView("key") person(q), - @RelationView("key") friend(p, q). - """)); - } - - @Test - void multipleClausesTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var dnf = Dnf.builder("Example") - .parameter(p) - .clause(friendView.call(p, q)) - .clause(friendView.call(q, p)) - .build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - @RelationView("key") friend(p, q) - ; - @RelationView("key") friend(q, p). - """)); - } - - @Test - void dnfTest() { - var p = new Variable("p"); - var q = new Variable("q"); - var r = new Variable("r"); - var s = new Variable("s"); - var person = new Symbol<>("person", 1, Boolean.class, false); - var personView = new KeyOnlyRelationView<>(person); - var friend = new Symbol<>("friend", 2, Boolean.class, false); - var friendView = new KeyOnlyRelationView<>(friend); - var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build(); - var dnf = Dnf.builder("Example") - .parameter(p) - .clause( - personView.call(p), - personView.call(q), - not(called.call(p, q)) - ) - .build(); - - assertThat(dnf.toString(), is(""" - pred Example(p) <-> - @RelationView("key") person(p), - @RelationView("key") person(q), - !(@Dnf Called(p, q)). - """)); - } -} diff --git a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java index 0cda22df..a61e2b65 100644 --- a/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java +++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java @@ -1,8 +1,8 @@ package tools.refinery.store.query.tests; import org.junit.jupiter.api.Test; -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.view.KeyOnlyRelationView; import tools.refinery.store.representation.Symbol; @@ -14,8 +14,8 @@ import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo class StructurallyEqualToTest { @Test void flatEqualsTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); @@ -27,8 +27,8 @@ class StructurallyEqualToTest { @Test void flatNotEqualsTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); @@ -41,8 +41,8 @@ class StructurallyEqualToTest { @Test void deepEqualsTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); @@ -58,8 +58,8 @@ class StructurallyEqualToTest { @Test void deepNotEqualsTest() { - var p = new Variable("p"); - var q = new Variable("q"); + var p = Variable.of("p"); + var q = Variable.of("q"); var person = new Symbol<>("Person", 1, Boolean.class, false); var personView = new KeyOnlyRelationView<>(person); @@ -72,6 +72,6 @@ class StructurallyEqualToTest { var assertion = structurallyEqualTo(expected); var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion)); - assertThat(error.getMessage(), containsString(" called from Expected ")); + assertThat(error.getMessage(), containsString(" called from Expected/1 ")); } } diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java index aaab2e7e..685957c9 100644 --- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java @@ -30,14 +30,14 @@ class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker { var inProgress = getInProgress(); int size = inProgress.size(); if (size <= 1) { - description.appendText("was ").appendValue(pair.left()); + description.appendText("was ").appendText(pair.left().toDefinitionString()); return; } var last = inProgress.get(size - 1); - description.appendText("expected ").appendValue(last.right()); + description.appendText("expected ").appendText(last.right().toDefinitionString()); for (int i = size - 2; i >= 0; i--) { - description.appendText(" called from ").appendText(inProgress.get(i).left().name()); + description.appendText(" called from ").appendText(inProgress.get(i).left().toString()); } - description.appendText(" was not structurally equal to ").appendValue(last.right()); + description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString()); } } diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java index 83614278..bf1c1b74 100644 --- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java @@ -1,7 +1,7 @@ package tools.refinery.store.query.tests; import org.hamcrest.Matcher; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; public final class QueryMatchers { private QueryMatchers() { diff --git a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java index a42396dd..a9a78f88 100644 --- a/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java +++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java @@ -2,7 +2,7 @@ package tools.refinery.store.query.tests; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.equality.DeepDnfEqualityChecker; public class StructurallyEqualTo extends TypeSafeMatcher { @@ -31,6 +31,6 @@ public class StructurallyEqualTo extends TypeSafeMatcher { @Override public void describeTo(Description description) { - description.appendText("structurally equal to ").appendValue(expected); + description.appendText("structurally equal to ").appendText(expected.toDefinitionString()); } } diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java index 99656da8..4f195e97 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java @@ -12,8 +12,6 @@ public non-sealed interface PartialInterpretation extends AnyPartialInterp Cursor getAll(); - Cursor getAllErrors(); - MergeResult merge(Tuple key, A value); C getConcrete(Tuple key); diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java index e602242e..de039dd9 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java @@ -3,7 +3,7 @@ package tools.refinery.store.reasoning; import tools.refinery.store.adapter.ModelAdapter; import tools.refinery.store.reasoning.representation.AnyPartialSymbol; import tools.refinery.store.reasoning.representation.PartialSymbol; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.ResultSet; public interface ReasoningAdapter extends ModelAdapter { diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java index 588c2711..4030d296 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java @@ -3,7 +3,7 @@ package tools.refinery.store.reasoning; import tools.refinery.store.adapter.ModelAdapterBuilder; import tools.refinery.store.model.ModelStore; import tools.refinery.store.reasoning.literal.Modality; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import java.util.Collection; import java.util.List; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java index 69c0f5eb..f6a6e414 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java @@ -3,7 +3,7 @@ package tools.refinery.store.reasoning; import tools.refinery.store.adapter.ModelStoreAdapter; import tools.refinery.store.model.Model; import tools.refinery.store.reasoning.representation.AnyPartialSymbol; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import java.util.Collection; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java index a7a56680..0acf0d49 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java @@ -4,7 +4,7 @@ import tools.refinery.store.model.Model; import tools.refinery.store.reasoning.ReasoningAdapter; import tools.refinery.store.reasoning.PartialInterpretation; import tools.refinery.store.reasoning.representation.PartialSymbol; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.ResultSet; public class ReasoningAdapterImpl implements ReasoningAdapter { diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java index 2860e2b9..e11b14bf 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java @@ -5,7 +5,7 @@ import tools.refinery.store.model.ModelStore; import tools.refinery.store.model.ModelStoreBuilder; import tools.refinery.store.reasoning.ReasoningBuilder; import tools.refinery.store.reasoning.literal.Modality; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder implements ReasoningBuilder { public ReasoningBuilderImpl(ModelStoreBuilder storeBuilder) { diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java index 763dad6d..ac06e68b 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java @@ -4,7 +4,7 @@ import tools.refinery.store.reasoning.ReasoningStoreAdapter; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStore; import tools.refinery.store.reasoning.representation.AnyPartialSymbol; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import java.util.Collection; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java index 966e080a..2b0e0f08 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java @@ -1,18 +1,18 @@ package tools.refinery.store.reasoning.lifting; import org.jetbrains.annotations.Nullable; -import tools.refinery.store.reasoning.literal.ModalDnfCallLiteral; -import tools.refinery.store.reasoning.Reasoning; -import tools.refinery.store.reasoning.literal.ModalRelationLiteral; -import tools.refinery.store.reasoning.literal.PartialRelationLiteral; -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.DnfBuilder; -import tools.refinery.store.query.DnfClause; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.dnf.Dnf; +import tools.refinery.store.query.dnf.DnfBuilder; +import tools.refinery.store.query.dnf.DnfClause; +import tools.refinery.store.query.literal.CallLiteral; import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.DnfCallLiteral; import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.query.term.DataVariable; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.reasoning.Reasoning; +import tools.refinery.store.reasoning.literal.ModalConstraint; import tools.refinery.store.reasoning.literal.Modality; +import tools.refinery.store.reasoning.literal.PartialLiterals; import tools.refinery.store.util.CycleDetectingMapper; import java.util.ArrayList; @@ -46,7 +46,10 @@ public class DnfLifter { private boolean liftClause(Modality modality, DnfClause clause, DnfBuilder builder) { boolean changed = false; - var quantifiedVariables = new HashSet<>(clause.quantifiedVariables()); + var quantifiedVariables = new HashSet<>(clause.boundVariables() + .stream() + .filter(DataVariable.class::isInstance) + .toList()); var literals = clause.literals(); var liftedLiterals = new ArrayList(literals.size()); for (var literal : literals) { @@ -65,8 +68,8 @@ public class DnfLifter { } } for (var quantifiedVariable : quantifiedVariables) { - // Quantify over variables that are not already quantified with the expected modality. - liftedLiterals.add(Reasoning.EXISTS.call(CallPolarity.POSITIVE, modality, + // Quantify over data variables that are not already quantified with the expected modality. + liftedLiterals.add(new CallLiteral(CallPolarity.POSITIVE, new ModalConstraint(modality, Reasoning.EXISTS), List.of(quantifiedVariable))); } builder.clause(liftedLiterals); @@ -75,33 +78,39 @@ public class DnfLifter { @Nullable private Variable isExistsLiteralForVariable(Modality modality, Literal literal) { - if (literal instanceof ModalRelationLiteral modalRelationLiteral && - modalRelationLiteral.getPolarity() == CallPolarity.POSITIVE && - modalRelationLiteral.getModality() == modality && - modalRelationLiteral.getTarget().equals(Reasoning.EXISTS)) { - return modalRelationLiteral.getArguments().get(0); + if (literal instanceof CallLiteral callLiteral && + callLiteral.getPolarity() == CallPolarity.POSITIVE && + callLiteral.getTarget() instanceof ModalConstraint modalConstraint && + modalConstraint.modality() == modality && + modalConstraint.constraint().equals(Reasoning.EXISTS)) { + return callLiteral.getArguments().get(0); } return null; } @Nullable private Literal liftLiteral(Modality modality, Literal literal) { - if (literal instanceof PartialRelationLiteral partialRelationLiteral) { - return new ModalRelationLiteral(modality, partialRelationLiteral); - } else if (literal instanceof DnfCallLiteral dnfCallLiteral) { - var polarity = dnfCallLiteral.getPolarity(); - var target = dnfCallLiteral.getTarget(); - var liftedTarget = lift(modality.commute(polarity), target); - if (target.equals(liftedTarget)) { - return null; + if (!(literal instanceof CallLiteral callLiteral)) { + return null; + } + var target = callLiteral.getTarget(); + if (target instanceof ModalConstraint modalTarget) { + var actualTarget = modalTarget.constraint(); + if (actualTarget instanceof Dnf dnf) { + var targetModality = modalTarget.modality(); + var liftedTarget = lift(targetModality, dnf); + return new CallLiteral(callLiteral.getPolarity(), liftedTarget, callLiteral.getArguments()); } - return new DnfCallLiteral(polarity, liftedTarget, dnfCallLiteral.getArguments()); - } else if (literal instanceof ModalDnfCallLiteral modalDnfCallLiteral) { - var liftedTarget = lift(modalDnfCallLiteral.getModality(), modalDnfCallLiteral.getTarget()); - return new DnfCallLiteral(modalDnfCallLiteral.getPolarity(), liftedTarget, - modalDnfCallLiteral.getArguments()); - } else { + // No more lifting to be done, pass any modal call to a partial symbol through. return null; + } else if (target instanceof Dnf dnf) { + var polarity = callLiteral.getPolarity(); + var liftedTarget = lift(modality.commute(polarity), dnf); + // Use == instead of equals(), because lift will return the same object by reference is there are no + // changes made during lifting. + return liftedTarget == target ? null : new CallLiteral(polarity, liftedTarget, callLiteral.getArguments()); + } else { + return PartialLiterals.addModality(callLiteral, modality); } } } diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java index 7aa98bf2..ec381bb8 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java @@ -1,9 +1,9 @@ package tools.refinery.store.reasoning.lifting; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.reasoning.literal.Modality; -public record ModalDnf(Modality modality, Dnf dnf) { +record ModalDnf(Modality modality, Dnf dnf) { @Override public String toString() { return "%s %s".formatted(modality, dnf.name()); diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java new file mode 100644 index 00000000..2fbb4607 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java @@ -0,0 +1,46 @@ +package tools.refinery.store.reasoning.literal; + +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.equality.LiteralEqualityHelper; +import tools.refinery.store.query.literal.LiteralReduction; +import tools.refinery.store.query.term.Sort; + +import java.util.List; + +public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint { + private static final String FORMAT = "%s %s"; + + @Override + public String name() { + return FORMAT.formatted(modality, constraint.name()); + } + + @Override + public List getSorts() { + return constraint.getSorts(); + } + + @Override + public LiteralReduction getReduction() { + return constraint.getReduction(); + } + + @Override + public boolean equals(LiteralEqualityHelper helper, Constraint other) { + if (getClass() != other.getClass()) { + return false; + } + var otherModalConstraint = (ModalConstraint) other; + return modality == otherModalConstraint.modality && constraint.equals(helper, otherModalConstraint.constraint); + } + + @Override + public String toReferenceString() { + return FORMAT.formatted(modality, constraint.toReferenceString()); + } + + @Override + public String toString() { + return FORMAT.formatted(modality, constraint); + } +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalDnfCallLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalDnfCallLiteral.java deleted file mode 100644 index 1090f1ae..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalDnfCallLiteral.java +++ /dev/null @@ -1,48 +0,0 @@ -package tools.refinery.store.reasoning.literal; - -import tools.refinery.store.query.Dnf; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.equality.LiteralEqualityHelper; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.DnfCallLiteral; -import tools.refinery.store.query.literal.LiteralReduction; -import tools.refinery.store.query.literal.PolarLiteral; -import tools.refinery.store.query.substitution.Substitution; - -import java.util.List; - -public class ModalDnfCallLiteral extends ModalLiteral implements PolarLiteral { - public ModalDnfCallLiteral(CallPolarity polarity, Modality modality, Dnf target, List arguments) { - super(polarity, modality, target, arguments); - } - - public ModalDnfCallLiteral(Modality modality, DnfCallLiteral baseLiteral) { - super(modality.commute(baseLiteral.getPolarity()), baseLiteral); - } - - @Override - public Class getTargetType() { - return Dnf.class; - } - - @Override - protected boolean targetEquals(LiteralEqualityHelper helper, Dnf otherTarget) { - return helper.dnfEqual(getTarget(), otherTarget); - } - - @Override - public ModalDnfCallLiteral substitute(Substitution substitution) { - return new ModalDnfCallLiteral(getPolarity(), getModality(), getTarget(), substituteArguments(substitution)); - } - - @Override - public ModalDnfCallLiteral negate() { - return new ModalDnfCallLiteral(getPolarity().negate(), getModality(), getTarget(), getArguments()); - } - - @Override - public LiteralReduction getReduction() { - var dnfReduction = getTarget().getReduction(); - return getPolarity().isPositive() ? dnfReduction : dnfReduction.negate(); - } -} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalLiteral.java deleted file mode 100644 index 5992f172..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalLiteral.java +++ /dev/null @@ -1,63 +0,0 @@ -package tools.refinery.store.reasoning.literal; - -import tools.refinery.store.query.RelationLike; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.equality.LiteralEqualityHelper; -import tools.refinery.store.query.literal.CallLiteral; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.Literal; - -import java.util.List; -import java.util.Objects; - -public abstract class ModalLiteral extends CallLiteral { - private final Modality modality; - - protected ModalLiteral(CallPolarity polarity, Modality modality, T target, List arguments) { - super(polarity, target, arguments); - this.modality = modality; - } - - protected ModalLiteral(Modality modality, CallLiteral baseLiteral) { - this(baseLiteral.getPolarity(), commute(modality, baseLiteral.getPolarity()), baseLiteral.getTarget(), - baseLiteral.getArguments()); - } - - public Modality getModality() { - return modality; - } - - @Override - public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) { - if (!super.equalsWithSubstitution(helper, other)) { - return false; - } - // If {@link CallLiteral#equalsWithSubstitution(LiteralEqualityHelper, Literal)} has returned {@code true}, - // we must have the same dynamic type as {@code other}. - var otherModalLiteral = (ModalLiteral) other; - return modality == otherModalLiteral.modality; - } - - @Override - protected String targetToString() { - return "%s %s".formatted(modality, super.targetToString()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - ModalLiteral that = (ModalLiteral) o; - return modality == that.modality; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), modality); - } - - private static Modality commute(Modality modality, CallPolarity polarity) { - return polarity.isPositive() ? modality : modality.negate(); - } -} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalRelationLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalRelationLiteral.java deleted file mode 100644 index 9c72bd37..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalRelationLiteral.java +++ /dev/null @@ -1,37 +0,0 @@ -package tools.refinery.store.reasoning.literal; - -import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.PolarLiteral; -import tools.refinery.store.query.substitution.Substitution; - -import java.util.List; - -public final class ModalRelationLiteral extends ModalLiteral - implements PolarLiteral { - public ModalRelationLiteral(CallPolarity polarity, Modality modality, PartialRelation target, - List arguments) { - super(polarity, modality, target, arguments); - } - - - public ModalRelationLiteral(Modality modality, PartialRelationLiteral baseLiteral) { - super(modality, baseLiteral); - } - - @Override - public Class getTargetType() { - return PartialRelation.class; - } - - @Override - public ModalRelationLiteral substitute(Substitution substitution) { - return new ModalRelationLiteral(getPolarity(), getModality(), getTarget(), substituteArguments(substitution)); - } - - @Override - public ModalRelationLiteral negate() { - return new ModalRelationLiteral(getPolarity().negate(), getModality(), getTarget(), getArguments()); - } -} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java index 10e4c7f7..f991f87f 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java @@ -1,33 +1,31 @@ package tools.refinery.store.reasoning.literal; -import tools.refinery.store.query.literal.DnfCallLiteral; +import tools.refinery.store.query.literal.CallLiteral; public final class PartialLiterals { private PartialLiterals() { throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); } - public ModalRelationLiteral may(PartialRelationLiteral literal) { - return new ModalRelationLiteral(Modality.MAY, literal); + public static CallLiteral may(CallLiteral literal) { + return addModality(literal, Modality.MAY); } - public ModalRelationLiteral must(PartialRelationLiteral literal) { - return new ModalRelationLiteral(Modality.MUST, literal); + public static CallLiteral must(CallLiteral literal) { + return addModality(literal, Modality.MUST); } - public ModalRelationLiteral current(PartialRelationLiteral literal) { - return new ModalRelationLiteral(Modality.CURRENT, literal); + public static CallLiteral current(CallLiteral literal) { + return addModality(literal, Modality.CURRENT); } - public ModalDnfCallLiteral may(DnfCallLiteral literal) { - return new ModalDnfCallLiteral(Modality.MAY, literal); - } - - public ModalDnfCallLiteral must(DnfCallLiteral literal) { - return new ModalDnfCallLiteral(Modality.MUST, literal); - } - - public ModalDnfCallLiteral current(DnfCallLiteral literal) { - return new ModalDnfCallLiteral(Modality.CURRENT, literal); + public static CallLiteral addModality(CallLiteral literal, Modality modality) { + var target = literal.getTarget(); + if (target instanceof ModalConstraint) { + throw new IllegalArgumentException("Literal %s already has modality".formatted(literal)); + } + var polarity = literal.getPolarity(); + var modalTarget = new ModalConstraint(modality.commute(polarity), target); + return new CallLiteral(polarity, modalTarget, literal.getArguments()); } } diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialRelationLiteral.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialRelationLiteral.java deleted file mode 100644 index aff84538..00000000 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialRelationLiteral.java +++ /dev/null @@ -1,32 +0,0 @@ -package tools.refinery.store.reasoning.literal; - -import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.literal.CallLiteral; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.PolarLiteral; -import tools.refinery.store.query.substitution.Substitution; - -import java.util.List; - -public final class PartialRelationLiteral extends CallLiteral - implements PolarLiteral { - public PartialRelationLiteral(CallPolarity polarity, PartialRelation target, List substitution) { - super(polarity, target, substitution); - } - - @Override - public Class getTargetType() { - return PartialRelation.class; - } - - @Override - public PartialRelationLiteral substitute(Substitution substitution) { - return new PartialRelationLiteral(getPolarity(), getTarget(), substituteArguments(substitution)); - } - - @Override - public PartialRelationLiteral negate() { - return new PartialRelationLiteral(getPolarity().negate(), getTarget(), getArguments()); - } -} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java index f884f8d6..9bae53a9 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java @@ -1,18 +1,16 @@ package tools.refinery.store.reasoning.representation; -import tools.refinery.store.reasoning.literal.Modality; -import tools.refinery.store.reasoning.literal.PartialRelationLiteral; -import tools.refinery.store.reasoning.literal.ModalRelationLiteral; -import tools.refinery.store.query.RelationLike; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.literal.CallPolarity; +import tools.refinery.store.query.Constraint; +import tools.refinery.store.query.term.NodeSort; +import tools.refinery.store.query.term.Sort; import tools.refinery.store.representation.AbstractDomain; import tools.refinery.store.representation.TruthValue; import tools.refinery.store.representation.TruthValueDomain; +import java.util.Arrays; import java.util.List; -public record PartialRelation(String name, int arity) implements PartialSymbol, RelationLike { +public record PartialRelation(String name, int arity) implements PartialSymbol, Constraint { @Override public AbstractDomain abstractDomain() { return TruthValueDomain.INSTANCE; @@ -28,24 +26,16 @@ public record PartialRelation(String name, int arity) implements PartialSymbol arguments) { - return new ModalRelationLiteral(polarity, modality, this, arguments); - } - - public PartialRelationLiteral call(CallPolarity polarity, List arguments) { - return new PartialRelationLiteral(polarity, this, arguments); - } - - public PartialRelationLiteral call(CallPolarity polarity, Variable... arguments) { - return call(polarity, List.of(arguments)); - } - - public PartialRelationLiteral call(Variable... arguments) { - return call(CallPolarity.POSITIVE, arguments); + @Override + public List getSorts() { + var sorts = new Sort[arity()]; + Arrays.fill(sorts, NodeSort.INSTANCE); + return List.of(sorts); } - public PartialRelationLiteral callTransitive(Variable left, Variable right) { - return call(CallPolarity.TRANSITIVE, List.of(left, right)); + @Override + public String toReferenceString() { + return name; } @Override diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java index c7681b53..e8ed05a3 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java @@ -3,7 +3,7 @@ package tools.refinery.store.reasoning.rule; import tools.refinery.store.reasoning.Reasoning; import tools.refinery.store.reasoning.representation.PartialRelation; import tools.refinery.store.model.Model; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.representation.TruthValue; import tools.refinery.store.tuple.Tuple; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java index 8a812518..c7b16d47 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java @@ -1,7 +1,7 @@ package tools.refinery.store.reasoning.rule; import tools.refinery.store.model.Model; -import tools.refinery.store.query.Dnf; +import tools.refinery.store.query.dnf.Dnf; import java.util.ArrayList; import java.util.HashSet; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java index bf980759..4753b8bc 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java @@ -1,7 +1,7 @@ package tools.refinery.store.reasoning.rule; import tools.refinery.store.model.Model; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import java.util.List; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java index 042c2636..90633495 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java @@ -1,4 +1,4 @@ -package tools.refinery.store.reasoning.translator; +package tools.refinery.store.reasoning.seed; import tools.refinery.store.map.Cursor; import tools.refinery.store.tuple.Tuple; @@ -6,7 +6,7 @@ import tools.refinery.store.tuple.Tuple; public interface Seed { int arity(); - T getReducedValue(); + T reducedValue(); T get(Tuple key); diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java new file mode 100644 index 00000000..a030f6ea --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java @@ -0,0 +1,22 @@ +package tools.refinery.store.reasoning.seed; + +import tools.refinery.store.map.Cursor; +import tools.refinery.store.tuple.Tuple; + +public record UniformSeed(int arity, T reducedValue) implements Seed { + public UniformSeed { + if (arity < 0) { + throw new IllegalArgumentException("Arity must not be negative"); + } + } + + @Override + public T get(Tuple key) { + return reducedValue; + } + + @Override + public Cursor getCursor(T defaultValue, int nodeCount) { + return null; + } +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java index 7909a7e1..5cdfedf7 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java @@ -2,7 +2,7 @@ package tools.refinery.store.reasoning.translator; import tools.refinery.store.reasoning.representation.AnyPartialSymbol; import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.query.Variable; +import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.literal.Literal; import tools.refinery.store.query.substitution.Substitutions; diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java new file mode 100644 index 00000000..9bab80c9 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java @@ -0,0 +1,22 @@ +package tools.refinery.store.reasoning.translator; + +import tools.refinery.store.model.Model; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.literal.CallPolarity; +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.reasoning.PartialInterpretation; +import tools.refinery.store.reasoning.literal.Modality; +import tools.refinery.store.reasoning.representation.PartialRelation; +import tools.refinery.store.representation.TruthValue; + +import java.util.List; + +public interface TranslatedRelation { + PartialRelation getSource(); + + void configure(List advices); + + List call(CallPolarity polarity, Modality modality, List arguments); + + PartialInterpretation createPartialInterpretation(Model model); +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java index e45d20c8..24b93911 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java @@ -1,48 +1,32 @@ package tools.refinery.store.reasoning.translator; -import tools.refinery.store.reasoning.ReasoningBuilder; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelStoreBuilder; -import tools.refinery.store.reasoning.AnyPartialInterpretation; -import tools.refinery.store.reasoning.literal.Modality; -import tools.refinery.store.reasoning.representation.AnyPartialSymbol; -import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.reasoning.ReasoningBuilder; import java.util.Collection; -import java.util.List; -import java.util.Map; public abstract class TranslationUnit { private ReasoningBuilder reasoningBuilder; - protected ReasoningBuilder getPartialInterpretationBuilder() { + protected ReasoningBuilder getReasoningBuilder() { return reasoningBuilder; } public void setPartialInterpretationBuilder(ReasoningBuilder reasoningBuilder) { this.reasoningBuilder = reasoningBuilder; + configureReasoningBuilder(); } protected ModelStoreBuilder getModelStoreBuilder() { return reasoningBuilder.getStoreBuilder(); } - public abstract Collection getTranslatedPartialSymbols(); - - public Collection computeAdvices() { - // No advices to give by default. - return List.of(); + protected void configureReasoningBuilder() { + // Nothing to configure by default. } - public abstract void configure(Collection advices); - - public abstract List call(CallPolarity polarity, Modality modality, PartialRelation target, - List arguments); - - public abstract Map createPartialInterpretations(Model model); + public abstract Collection getTranslatedRelations(); public abstract void initializeModel(Model model, int nodeCount); } diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java new file mode 100644 index 00000000..b703f142 --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java @@ -0,0 +1,88 @@ +package tools.refinery.store.reasoning.translator.base; + +import tools.refinery.store.map.Cursor; +import tools.refinery.store.model.Interpretation; +import tools.refinery.store.query.ResultSet; +import tools.refinery.store.reasoning.MergeResult; +import tools.refinery.store.reasoning.PartialInterpretation; +import tools.refinery.store.reasoning.ReasoningAdapter; +import tools.refinery.store.reasoning.representation.PartialRelation; +import tools.refinery.store.representation.TruthValue; +import tools.refinery.store.tuple.Tuple; + +public class BaseDecisionInterpretation implements PartialInterpretation { + private final ReasoningAdapter reasoningAdapter; + private PartialRelation partialRelation; + private final ResultSet mustResultSet; + private final ResultSet mayResultSet; + private final ResultSet errorResultSet; + private final ResultSet currentResultSet; + private final Interpretation interpretation; + + public BaseDecisionInterpretation(ReasoningAdapter reasoningAdapter, ResultSet mustResultSet, + ResultSet mayResultSet, ResultSet errorResultSet, + ResultSet currentResultSet, Interpretation interpretation) { + this.reasoningAdapter = reasoningAdapter; + this.mustResultSet = mustResultSet; + this.mayResultSet = mayResultSet; + this.errorResultSet = errorResultSet; + this.currentResultSet = currentResultSet; + this.interpretation = interpretation; + } + + @Override + public ReasoningAdapter getAdapter() { + return reasoningAdapter; + } + + @Override + public int countUnfinished() { + return 0; + } + + @Override + public int countErrors() { + return errorResultSet.size(); + } + + @Override + public PartialRelation getPartialSymbol() { + return partialRelation; + } + + @Override + public TruthValue get(Tuple key) { + return null; + } + + @Override + public Cursor getAll() { + return null; + } + + @Override + public MergeResult merge(Tuple key, TruthValue value) { + TruthValue newValue; + switch (value) { + case UNKNOWN -> { + return MergeResult.UNCHANGED; + } + case TRUE -> newValue = mayResultSet.get(key) ? TruthValue.TRUE : TruthValue.ERROR; + case FALSE -> newValue = mustResultSet.get(key) ? TruthValue.ERROR : TruthValue.FALSE; + case ERROR -> newValue = TruthValue.ERROR; + default -> throw new IllegalArgumentException("Unknown truth value: " + value); + } + var oldValue = interpretation.put(key, newValue); + return oldValue == TruthValue.ERROR ? MergeResult.UNCHANGED : MergeResult.REFINED; + } + + @Override + public Boolean getConcrete(Tuple key) { + return currentResultSet.get(key); + } + + @Override + public Cursor getAllConcrete() { + return null; + } +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java new file mode 100644 index 00000000..36e2782a --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java @@ -0,0 +1,49 @@ +package tools.refinery.store.reasoning.translator.base; + +import tools.refinery.store.model.Model; +import tools.refinery.store.reasoning.representation.PartialRelation; +import tools.refinery.store.reasoning.seed.Seed; +import tools.refinery.store.reasoning.seed.UniformSeed; +import tools.refinery.store.reasoning.translator.TranslatedRelation; +import tools.refinery.store.reasoning.translator.TranslationUnit; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; + +import java.util.Collection; +import java.util.List; + +public class BaseDecisionTranslationUnit extends TranslationUnit { + private final PartialRelation partialRelation; + private final Seed seed; + private final Symbol symbol; + + public BaseDecisionTranslationUnit(PartialRelation partialRelation, Seed seed) { + if (seed.arity() != partialRelation.arity()) { + throw new IllegalArgumentException("Expected seed with arity %d for %s, got arity %s" + .formatted(partialRelation.arity(), partialRelation, seed.arity())); + } + this.partialRelation = partialRelation; + this.seed = seed; + symbol = new Symbol<>(partialRelation.name(), partialRelation.arity(), TruthValue.class, TruthValue.UNKNOWN); + } + + public BaseDecisionTranslationUnit(PartialRelation partialRelation) { + this(partialRelation, new UniformSeed<>(partialRelation.arity(), TruthValue.UNKNOWN)); + } + + @Override + protected void configureReasoningBuilder() { + getModelStoreBuilder().symbol(symbol); + } + + @Override + public Collection getTranslatedRelations() { + return List.of(new TranslatedBaseDecision(getReasoningBuilder(), partialRelation, symbol)); + } + + @Override + public void initializeModel(Model model, int nodeCount) { + var interpretation = model.getInterpretation(symbol); + interpretation.putAll(seed.getCursor(TruthValue.UNKNOWN, nodeCount)); + } +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java new file mode 100644 index 00000000..2294b4fd --- /dev/null +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java @@ -0,0 +1,49 @@ +package tools.refinery.store.reasoning.translator.base; + +import tools.refinery.store.model.Model; +import tools.refinery.store.query.term.Variable; +import tools.refinery.store.query.literal.CallPolarity; +import tools.refinery.store.query.literal.Literal; +import tools.refinery.store.reasoning.PartialInterpretation; +import tools.refinery.store.reasoning.ReasoningBuilder; +import tools.refinery.store.reasoning.literal.Modality; +import tools.refinery.store.reasoning.representation.PartialRelation; +import tools.refinery.store.reasoning.translator.Advice; +import tools.refinery.store.reasoning.translator.TranslatedRelation; +import tools.refinery.store.representation.Symbol; +import tools.refinery.store.representation.TruthValue; + +import java.util.List; + +class TranslatedBaseDecision implements TranslatedRelation { + private final ReasoningBuilder reasoningBuilder; + private final PartialRelation partialRelation; + private final Symbol symbol; + + public TranslatedBaseDecision(ReasoningBuilder reasoningBuilder, PartialRelation partialRelation, + Symbol symbol) { + this.reasoningBuilder = reasoningBuilder; + this.partialRelation = partialRelation; + this.symbol = symbol; + } + + @Override + public PartialRelation getSource() { + return partialRelation; + } + + @Override + public void configure(List advices) { + + } + + @Override + public List call(CallPolarity polarity, Modality modality, List arguments) { + return null; + } + + @Override + public PartialInterpretation createPartialInterpretation(Model model) { + return null; + } +} diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java index c800b4cd..4b0761f2 100644 --- a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java +++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java @@ -1,18 +1,14 @@ package tools.refinery.store.reasoning.translator.typehierarchy; -import tools.refinery.store.reasoning.AnyPartialInterpretation; -import tools.refinery.store.reasoning.literal.Modality; -import tools.refinery.store.reasoning.representation.AnyPartialSymbol; +import tools.refinery.store.model.Model; import tools.refinery.store.reasoning.representation.PartialRelation; -import tools.refinery.store.reasoning.translator.Advice; +import tools.refinery.store.reasoning.translator.TranslatedRelation; import tools.refinery.store.reasoning.translator.TranslationUnit; -import tools.refinery.store.model.Model; -import tools.refinery.store.query.Variable; -import tools.refinery.store.query.literal.CallPolarity; -import tools.refinery.store.query.literal.Literal; import tools.refinery.store.representation.Symbol; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; public class TypeHierarchyTranslationUnit extends TranslationUnit { static final Symbol INFERRED_TYPE_SYMBOL = new Symbol<>("inferredType", 1, @@ -25,24 +21,8 @@ public class TypeHierarchyTranslationUnit extends TranslationUnit { } @Override - public Collection getTranslatedPartialSymbols() { - return null; - } - - @Override - public void configure(Collection advices) { - - } - - @Override - public List call(CallPolarity polarity, Modality modality, PartialRelation target, - List arguments) { - return null; - } - - @Override - public Map createPartialInterpretations(Model model) { - return null; + public Collection getTranslatedRelations() { + return List.of(); } @Override diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java index 470ca298..953ea9f8 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java @@ -1,5 +1,8 @@ package tools.refinery.store.tuple; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + public interface TupleLike { int getSize(); @@ -22,4 +25,11 @@ public interface TupleLike { default -> Tuple.of(toArray()); }; } + + static String toString(TupleLike tuple) { + var valuesString = IntStream.range(0, tuple.getSize()) + .mapToObj(i -> Integer.toString(tuple.get(i))) + .collect(Collectors.joining(", ")); + return "[" + valuesString + "]"; + } } diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java index 15fd063b..c3aed847 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java @@ -1,7 +1,6 @@ package tools.refinery.store.tuple; import java.util.Arrays; -import java.util.stream.Collectors; public record TupleN(int[] values) implements Tuple { static final int CUSTOM_TUPLE_SIZE = 2; @@ -29,8 +28,7 @@ public record TupleN(int[] values) implements Tuple { @Override public String toString() { - var valuesString = Arrays.stream(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")); - return "[" + valuesString + "]"; + return TupleLike.toString(this); } @Override -- cgit v1.2.3-54-g00ecf