aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2023-03-31 17:31:46 +0200
committerLibravatar GitHub <noreply@github.com>2023-03-31 17:31:46 +0200
commit93f1d439f33a5139039fe93280bbfcae61a904ab (patch)
treee98eae681a866d2d0cd728885ed6c8f8fa65e9a2 /subprojects
parentrefactor: PartialInterpretation adapter naming (diff)
parentbuild: try to fix secret detection in workflow (diff)
downloadrefinery-93f1d439f33a5139039fe93280bbfcae61a904ab.tar.gz
refinery-93f1d439f33a5139039fe93280bbfcae61a904ab.tar.zst
refinery-93f1d439f33a5139039fe93280bbfcae61a904ab.zip
Merge pull request #24 from kris7t/partial-interpretation
Changes for supporting partial interpretation
Diffstat (limited to 'subprojects')
-rw-r--r--subprojects/frontend/package.json68
-rw-r--r--subprojects/frontend/src/Refinery.tsx1
-rw-r--r--subprojects/frontend/src/UpdateNotification.tsx4
-rw-r--r--subprojects/frontend/src/editor/scrollbarViewPlugin.ts1
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx8
-rw-r--r--subprojects/frontend/src/utils/PendingTask.ts1
-rw-r--r--subprojects/frontend/src/utils/useDelayedSnackbar.ts1
-rw-r--r--subprojects/frontend/src/xtext/webSocketMachine.ts11
-rw-r--r--subprojects/frontend/tsconfig.base.json5
-rw-r--r--subprojects/language-model/META-INF/MANIFEST.MF2
-rw-r--r--subprojects/store-query-viatra/build.gradle2
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraModelQueryBuilder.java15
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryAdapterImpl.java79
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryBuilderImpl.java86
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/ViatraModelQueryStoreAdapterImpl.java29
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalQueryMetaContext.java19
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/context/RelationalRuntimeContext.java31
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendOperationExecutor.java75
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/ExtendPositivePatternCall.java116
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/FlatCostFunction.java30
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/GenericTypeExtend.java136
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchBackendFactory.java55
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalLocalSearchResultProvider.java23
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/localsearch/RelationalOperationCompiler.java65
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalCursor.java48
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/FunctionalViatraMatcher.java91
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java48
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/MatcherUtils.java50
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/OmitOutputViatraTupleLike.java23
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RawPatternMatcher.java15
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalCursor.java42
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/RelationalViatraMatcher.java89
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/UnsafeFunctionalCursor.java52
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/ViatraTupleLike.java (renamed from subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/ViatraTupleLike.java)9
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/AssumptionEvaluator.java16
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java223
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java256
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/QueryWrapperFactory.java173
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPQuery.java1
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java72
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RelationViewWrapper.java5
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatefulMultisetAggregator.java60
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/StatelessMultisetAggregator.java50
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/TermEvaluator.java32
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/ValueProviderBasedValuation.java14
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/ModelUpdateListener.java3
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/RelationViewUpdateListener.java22
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TupleChangingRelationViewUpdateListener.java6
-rw-r--r--subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingRelationViewUpdateListener.java5
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/DiagonalQueryTest.java471
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/FunctionalQueryTest.java607
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTest.java564
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/QueryTransactionTest.java387
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryAssertions.java52
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryBackendHint.java22
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEngineTest.java16
-rw-r--r--subprojects/store-query-viatra/src/test/java/tools/refinery/store/query/viatra/tests/QueryEvaluationHintSource.java19
-rw-r--r--subprojects/store-query/build.gradle9
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/AnyResultSet.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/Constraint.java65
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java)0
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java)8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java)8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java)3
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/AnyQuery.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Dnf.java194
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfBuilder.java110
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfClause.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java)6
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java)2
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQuery.java103
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalQueryBuilder.java46
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/Query.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/QueryBuilder.java71
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/RelationalQuery.java93
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java31
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AbstractCallLiteral.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AggregationLiteral.java113
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssignLiteral.java44
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/AssumeLiteral.java53
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java53
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java102
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java)10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CanNegate.java5
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CountLiteral.java83
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/CompositeSubstitution.java10
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java15
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java18
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java33
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Aggregator.java13
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyDataVariable.java33
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AnyTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryOperator.java26
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticBinaryTerm.java56
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryOperator.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ArithmeticUnaryTerm.java51
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/AssignedValue.java8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/BinaryTerm.java93
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonOperator.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ComparisonTerm.java63
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ConstantTerm.java52
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataSort.java29
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/DataVariable.java87
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/ExtremeValueAggregator.java103
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeSort.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/NodeVariable.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/OpaqueTerm.java80
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Sort.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregate.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatefulAggregator.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/StatelessAggregator.java20
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Term.java21
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/UnaryTerm.java68
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/Variable.java76
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolConstantTerm.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolLogicBinaryTerm.java78
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolNotTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/BoolTerms.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/bool/LogicBinaryOperator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticBinaryTerm.java48
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntArithmeticUnaryTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntComparisonTerm.java34
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntExtremeValueAggregator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntSumAggregator.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/IntTerms.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/int_/RealToIntTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/IntToRealTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticBinaryTerm.java36
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealArithmeticUnaryTerm.java30
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealComparisonTerm.java35
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealExtremeValueAggregator.java17
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealSumAggregator.java85
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/term/real/RealTerms.java81
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/RestrictedValuation.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/SubstitutedValuation.java11
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/valuation/Valuation.java23
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/AnyRelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/AnyRelationView.java)8
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java)0
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/ForbiddenRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/FunctionalRelationView.java)38
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java)0
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MayRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/MustRelationView.java16
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java)27
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java)0
-rw-r--r--subprojects/store-query/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/view/TuplePreservingRelationView.java)13
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java220
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/DnfToDefinitionStringTest.java174
-rw-r--r--subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java77
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java43
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java14
-rw-r--r--subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java36
-rw-r--r--subprojects/store-reasoning/build.gradle7
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java13
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java15
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java20
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/Reasoning.java24
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java28
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java17
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java29
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java37
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java116
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/ModalConstraint.java46
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java (renamed from subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java)11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java31
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java11
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java56
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java12
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java36
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java38
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java12
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleActionExecutor.java9
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleExecutor.java34
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java14
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/UniformSeed.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java159
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java25
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslatedRelation.java22
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionInterpretation.java88
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/BaseDecisionTranslationUnit.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/base/TranslatedBaseDecision.java49
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java6
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java101
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeRelationView.java19
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeRelationView.java19
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java30
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java136
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java4
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java202
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java32
-rw-r--r--subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java46
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java32
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java203
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java200
-rw-r--r--subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java51
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java36
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java19
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java24
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java23
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/DNF.java169
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java11
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java25
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/Variable.java43
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java80
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java12
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java9
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java32
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java17
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java32
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java29
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java7
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java60
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleLike.java10
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/TupleN.java4
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java52
-rw-r--r--subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java2
239 files changed, 10460 insertions, 1260 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index a9153cd4..a761e74c 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -2,6 +2,7 @@
2 "name": "@refinery/frontend", 2 "name": "@refinery/frontend",
3 "version": "0.0.0", 3 "version": "0.0.0",
4 "description": "Web frontend for Refinery", 4 "description": "Web frontend for Refinery",
5 "type": "module",
5 "private": true, 6 "private": true,
6 "scripts": { 7 "scripts": {
7 "build": "cross-env MODE=production vite build", 8 "build": "cross-env MODE=production vite build",
@@ -23,60 +24,61 @@
23 }, 24 },
24 "homepage": "https://refinery.tools", 25 "homepage": "https://refinery.tools",
25 "dependencies": { 26 "dependencies": {
26 "@codemirror/autocomplete": "^6.4.0", 27 "@codemirror/autocomplete": "^6.4.2",
27 "@codemirror/commands": "^6.2.0", 28 "@codemirror/commands": "^6.2.2",
28 "@codemirror/language": "^6.4.0", 29 "@codemirror/language": "^6.6.0",
29 "@codemirror/lint": "^6.1.0", 30 "@codemirror/lint": "^6.2.0",
30 "@codemirror/search": "^6.2.3", 31 "@codemirror/search": "^6.3.0",
31 "@codemirror/state": "^6.2.0", 32 "@codemirror/state": "^6.2.0",
32 "@codemirror/view": "^6.7.3", 33 "@codemirror/view": "^6.9.3",
33 "@emotion/react": "^11.10.5", 34 "@emotion/react": "^11.10.6",
34 "@emotion/styled": "^11.10.5", 35 "@emotion/styled": "^11.10.6",
35 "@fontsource/inter": "^4.5.15", 36 "@fontsource/inter": "^4.5.15",
36 "@fontsource/jetbrains-mono": "^4.5.12", 37 "@fontsource/jetbrains-mono": "^4.5.12",
37 "@lezer/common": "^1.0.2", 38 "@lezer/common": "^1.0.2",
38 "@lezer/highlight": "^1.1.3", 39 "@lezer/highlight": "^1.1.4",
39 "@lezer/lr": "^1.3.3", 40 "@lezer/lr": "^1.3.3",
40 "@material-icons/svg": "^1.0.33", 41 "@material-icons/svg": "^1.0.33",
41 "@mui/icons-material": "5.11.0", 42 "@mui/icons-material": "5.11.11",
42 "@mui/material": "5.11.7", 43 "@mui/material": "5.11.15",
43 "@vitejs/plugin-react-swc": "^3.1.0", 44 "@vitejs/plugin-react-swc": "^3.2.0",
44 "ansi-styles": "^6.2.1", 45 "ansi-styles": "^6.2.1",
46 "csstype": "^3.1.1",
45 "escape-string-regexp": "^5.0.0", 47 "escape-string-regexp": "^5.0.0",
46 "lodash-es": "^4.17.21", 48 "lodash-es": "^4.17.21",
47 "loglevel": "^1.8.1", 49 "loglevel": "^1.8.1",
48 "loglevel-plugin-prefix": "^0.8.4", 50 "loglevel-plugin-prefix": "^0.8.4",
49 "mobx": "^6.7.0", 51 "mobx": "^6.9.0",
50 "mobx-react-lite": "^3.4.0", 52 "mobx-react-lite": "^3.4.3",
51 "ms": "^2.1.3", 53 "ms": "^2.1.3",
52 "nanoid": "^4.0.0", 54 "nanoid": "^4.0.2",
53 "notistack": "^2.0.8", 55 "notistack": "^3.0.1",
54 "react": "^18.2.0", 56 "react": "^18.2.0",
55 "react-dom": "^18.2.0", 57 "react-dom": "^18.2.0",
56 "xstate": "^4.35.4", 58 "xstate": "^4.37.1",
57 "zod": "^3.20.2" 59 "zod": "^3.21.4"
58 }, 60 },
59 "devDependencies": { 61 "devDependencies": {
60 "@lezer/generator": "^1.2.2", 62 "@lezer/generator": "^1.2.2",
61 "@tsconfig/node18-strictest-esm": "^1.0.1", 63 "@tsconfig/strictest": "^2.0.0",
62 "@types/eslint": "^8.21.0", 64 "@types/eslint": "^8.37.0",
63 "@types/html-minifier-terser": "^7.0.0", 65 "@types/html-minifier-terser": "^7.0.0",
64 "@types/lodash-es": "^4.17.6", 66 "@types/lodash-es": "^4.17.7",
65 "@types/micromatch": "^4.0.2", 67 "@types/micromatch": "^4.0.2",
66 "@types/ms": "^0.7.31", 68 "@types/ms": "^0.7.31",
67 "@types/node": "^18.11.18", 69 "@types/node": "^18.15.11",
68 "@types/prettier": "^2.7.2", 70 "@types/prettier": "^2.7.2",
69 "@types/react": "^18.0.27", 71 "@types/react": "^18.0.31",
70 "@types/react-dom": "^18.0.10", 72 "@types/react-dom": "^18.0.11",
71 "@typescript-eslint/eslint-plugin": "^5.50.0", 73 "@typescript-eslint/eslint-plugin": "^5.57.0",
72 "@typescript-eslint/parser": "^5.50.0", 74 "@typescript-eslint/parser": "^5.57.0",
73 "@xstate/cli": "^0.4.2", 75 "@xstate/cli": "^0.4.2",
74 "cross-env": "^7.0.3", 76 "cross-env": "^7.0.3",
75 "eslint": "^8.33.0", 77 "eslint": "^8.37.0",
76 "eslint-config-airbnb": "^19.0.4", 78 "eslint-config-airbnb": "^19.0.4",
77 "eslint-config-airbnb-typescript": "^17.0.0", 79 "eslint-config-airbnb-typescript": "^17.0.0",
78 "eslint-config-prettier": "^8.6.0", 80 "eslint-config-prettier": "^8.8.0",
79 "eslint-import-resolver-typescript": "^3.5.3", 81 "eslint-import-resolver-typescript": "^3.5.4",
80 "eslint-plugin-import": "^2.27.5", 82 "eslint-plugin-import": "^2.27.5",
81 "eslint-plugin-jsx-a11y": "^6.7.1", 83 "eslint-plugin-jsx-a11y": "^6.7.1",
82 "eslint-plugin-mobx": "^0.0.9", 84 "eslint-plugin-mobx": "^0.0.9",
@@ -85,10 +87,10 @@
85 "eslint-plugin-react-hooks": "^4.6.0", 87 "eslint-plugin-react-hooks": "^4.6.0",
86 "html-minifier-terser": "^7.1.0", 88 "html-minifier-terser": "^7.1.0",
87 "micromatch": "^4.0.5", 89 "micromatch": "^4.0.5",
88 "prettier": "^2.8.3", 90 "prettier": "^2.8.7",
89 "typescript": "4.9.5", 91 "typescript": "5.0.3",
90 "vite": "^4.1.1", 92 "vite": "^4.2.1",
91 "vite-plugin-pwa": "^0.14.1", 93 "vite-plugin-pwa": "^0.14.7",
92 "workbox-window": "^6.5.4" 94 "workbox-window": "^6.5.4"
93 } 95 }
94} 96}
diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx
index 93a82ee1..f0162349 100644
--- a/subprojects/frontend/src/Refinery.tsx
+++ b/subprojects/frontend/src/Refinery.tsx
@@ -8,7 +8,6 @@ import EditorPane from './editor/EditorPane';
8 8
9export default function Refinery(): JSX.Element { 9export default function Refinery(): JSX.Element {
10 return ( 10 return (
11 // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes
12 <SnackbarProvider TransitionComponent={Grow}> 11 <SnackbarProvider TransitionComponent={Grow}>
13 <UpdateNotification /> 12 <UpdateNotification />
14 <Stack direction="column" height="100%" overflow="auto"> 13 <Stack direction="column" height="100%" overflow="auto">
diff --git a/subprojects/frontend/src/UpdateNotification.tsx b/subprojects/frontend/src/UpdateNotification.tsx
index 07f7f5f7..5c8c2d01 100644
--- a/subprojects/frontend/src/UpdateNotification.tsx
+++ b/subprojects/frontend/src/UpdateNotification.tsx
@@ -32,14 +32,14 @@ export default observer(function UpdateNotification(): null {
32 return enqueueLater('Failed to download update', { 32 return enqueueLater('Failed to download update', {
33 variant: 'error', 33 variant: 'error',
34 action: ( 34 action: (
35 <> 35 <ContrastThemeProvider>
36 <Button color="inherit" onClick={() => pwaStore.checkForUpdates()}> 36 <Button color="inherit" onClick={() => pwaStore.checkForUpdates()}>
37 Try again 37 Try again
38 </Button> 38 </Button>
39 <Button color="inherit" onClick={() => pwaStore.dismissError()}> 39 <Button color="inherit" onClick={() => pwaStore.dismissError()}>
40 Dismiss 40 Dismiss
41 </Button> 41 </Button>
42 </> 42 </ContrastThemeProvider>
43 ), 43 ),
44 }); 44 });
45 } 45 }
diff --git a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
index f54251a9..f44034fd 100644
--- a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
+++ b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
@@ -266,7 +266,6 @@ export default function scrollbarViewPlugin(
266 if (firstRunTimeout !== undefined) { 266 if (firstRunTimeout !== undefined) {
267 clearTimeout(firstRunTimeout); 267 clearTimeout(firstRunTimeout);
268 } 268 }
269 // @ts-expect-error `@types/node` typings should not be in effect here.
270 firstRunTimeout = setTimeout(() => { 269 firstRunTimeout = setTimeout(() => {
271 spacer.style.minHeight = `${scrollHeight}px`; 270 spacer.style.minHeight = `${scrollHeight}px`;
272 firstRun = false; 271 firstRun = false;
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx
index 7bda1ede..ff97d524 100644
--- a/subprojects/frontend/src/theme/ThemeProvider.tsx
+++ b/subprojects/frontend/src/theme/ThemeProvider.tsx
@@ -6,7 +6,6 @@ import {
6 type ThemeOptions, 6 type ThemeOptions,
7 ThemeProvider as MaterialUiThemeProvider, 7 ThemeProvider as MaterialUiThemeProvider,
8 type TypographyStyle, 8 type TypographyStyle,
9 useTheme,
10 type CSSObject, 9 type CSSObject,
11} from '@mui/material/styles'; 10} from '@mui/material/styles';
12import { observer } from 'mobx-react-lite'; 11import { observer } from 'mobx-react-lite';
@@ -350,15 +349,14 @@ export function ContrastThemeProvider({
350}: { 349}: {
351 children?: ReactNode; 350 children?: ReactNode;
352}): JSX.Element { 351}): JSX.Element {
353 const theme = useTheme();
354 const contrastTheme = useContext(ContrastThemeContext); 352 const contrastTheme = useContext(ContrastThemeContext);
355 if (!contrastTheme) { 353 if (!contrastTheme) {
356 throw new Error('ContrastThemeProvider must be used within ThemeProvider'); 354 throw new Error('ContrastThemeProvider must be used within ThemeProvider');
357 } 355 }
358 return ( 356 return (
359 <ThemeAndContrastThemeProvider theme={contrastTheme} contrastTheme={theme}> 357 <MaterialUiThemeProvider theme={contrastTheme}>
360 {children} 358 {children}
361 </ThemeAndContrastThemeProvider> 359 </MaterialUiThemeProvider>
362 ); 360 );
363} 361}
364 362
@@ -378,7 +376,7 @@ const ThemeProvider = observer(function ThemeProvider({
378 return ( 376 return (
379 <ThemeAndContrastThemeProvider 377 <ThemeAndContrastThemeProvider
380 theme={darkMode ? darkTheme : lightTheme} 378 theme={darkMode ? darkTheme : lightTheme}
381 contrastTheme={darkMode ? lightTheme : darkTheme} 379 contrastTheme={darkTheme}
382 > 380 >
383 {children} 381 {children}
384 </ThemeAndContrastThemeProvider> 382 </ThemeAndContrastThemeProvider>
diff --git a/subprojects/frontend/src/utils/PendingTask.ts b/subprojects/frontend/src/utils/PendingTask.ts
index fd52cef1..d0b24c1f 100644
--- a/subprojects/frontend/src/utils/PendingTask.ts
+++ b/subprojects/frontend/src/utils/PendingTask.ts
@@ -20,7 +20,6 @@ export default class PendingTask<T> {
20 ) { 20 ) {
21 this.resolveCallback = resolveCallback; 21 this.resolveCallback = resolveCallback;
22 this.rejectCallback = rejectCallback; 22 this.rejectCallback = rejectCallback;
23 // @ts-expect-error See https://github.com/mobxjs/mobx/issues/3582 on `@types/node` pollution
24 this.timeout = setTimeout(() => { 23 this.timeout = setTimeout(() => {
25 if (!this.resolved) { 24 if (!this.resolved) {
26 this.reject(new TimeoutError()); 25 this.reject(new TimeoutError());
diff --git a/subprojects/frontend/src/utils/useDelayedSnackbar.ts b/subprojects/frontend/src/utils/useDelayedSnackbar.ts
index 54716c0c..03ad6caa 100644
--- a/subprojects/frontend/src/utils/useDelayedSnackbar.ts
+++ b/subprojects/frontend/src/utils/useDelayedSnackbar.ts
@@ -21,7 +21,6 @@ export default function useDelayedSnackbar(
21 delay = defaultDelay, 21 delay = defaultDelay,
22 ) => { 22 ) => {
23 let key: SnackbarKey | undefined; 23 let key: SnackbarKey | undefined;
24 // @ts-expect-error See https://github.com/mobxjs/mobx/issues/3582 on `@types/node` pollution
25 let timeout: number | undefined = setTimeout(() => { 24 let timeout: number | undefined = setTimeout(() => {
26 timeout = undefined; 25 timeout = undefined;
27 key = enqueueSnackbar(message, options); 26 key = enqueueSnackbar(message, options);
diff --git a/subprojects/frontend/src/xtext/webSocketMachine.ts b/subprojects/frontend/src/xtext/webSocketMachine.ts
index 216ed86a..fc53fef3 100644
--- a/subprojects/frontend/src/xtext/webSocketMachine.ts
+++ b/subprojects/frontend/src/xtext/webSocketMachine.ts
@@ -1,5 +1,5 @@
1import ms from 'ms'; 1import ms from 'ms';
2import { actions, assign, createMachine, RaiseAction } from 'xstate'; 2import { actions, assign, createMachine } from 'xstate';
3 3
4const { raise } = actions; 4const { raise } = actions;
5 5
@@ -217,16 +217,15 @@ export default createMachine(
217 ...context, 217 ...context,
218 errors: [], 218 errors: [],
219 })), 219 })),
220 // Workaround from https://github.com/statelyai/xstate/issues/1414#issuecomment-699972485
221 raiseTimeoutError: raise({ 220 raiseTimeoutError: raise({
222 type: 'ERROR', 221 type: 'ERROR',
223 message: 'Open timeout', 222 message: 'Open timeout',
224 }) as RaiseAction<WebSocketEvent>, 223 }),
225 raisePromiseRejectionError: (_context, { data }) => 224 raisePromiseRejectionError: (_context, { data }) =>
226 raise({ 225 raise<WebSocketContext, WebSocketEvent>({
227 type: 'ERROR', 226 type: 'ERROR',
228 message: data, 227 message: String(data),
229 }) as RaiseAction<WebSocketEvent>, 228 }),
230 }, 229 },
231 }, 230 },
232); 231);
diff --git a/subprojects/frontend/tsconfig.base.json b/subprojects/frontend/tsconfig.base.json
index 585866f1..b960e93c 100644
--- a/subprojects/frontend/tsconfig.base.json
+++ b/subprojects/frontend/tsconfig.base.json
@@ -1,7 +1,10 @@
1{ 1{
2 "extends": "@tsconfig/node18-strictest-esm/tsconfig.json", 2 "extends": "@tsconfig/strictest",
3 "compilerOptions": { 3 "compilerOptions": {
4 "useDefineForClassFields": true, 4 "useDefineForClassFields": true,
5 "verbatimModuleSyntax": false,
5 "isolatedModules": true, 6 "isolatedModules": true,
7 "module": "es2022",
8 "moduleResolution": "node"
6 } 9 }
7} 10}
diff --git a/subprojects/language-model/META-INF/MANIFEST.MF b/subprojects/language-model/META-INF/MANIFEST.MF
index a9c6340a..5d36a475 100644
--- a/subprojects/language-model/META-INF/MANIFEST.MF
+++ b/subprojects/language-model/META-INF/MANIFEST.MF
@@ -7,7 +7,7 @@ Bundle-Version: 0.0.0.qualifier
7Bundle-ClassPath: . 7Bundle-ClassPath: .
8Bundle-Vendor: %providerName 8Bundle-Vendor: %providerName
9Bundle-Localization: plugin 9Bundle-Localization: plugin
10Bundle-RequiredExecutionEnvironment: JavaSE-17 10Bundle-RequiredExecutionEnvironment: J2SE-1.5
11Export-Package: tools.refinery.language.model, 11Export-Package: tools.refinery.language.model,
12 tools.refinery.language.model.problem, 12 tools.refinery.language.model.problem,
13 tools.refinery.language.model.problem.impl, 13 tools.refinery.language.model.problem.impl,
diff --git a/subprojects/store-query-viatra/build.gradle b/subprojects/store-query-viatra/build.gradle
index c12b48fe..13a7544f 100644
--- a/subprojects/store-query-viatra/build.gradle
+++ b/subprojects/store-query-viatra/build.gradle
@@ -10,7 +10,7 @@ configurations.testRuntimeClasspath {
10dependencies { 10dependencies {
11 implementation libs.ecore 11 implementation libs.ecore
12 api libs.viatra 12 api libs.viatra
13 api project(':refinery-store') 13 api project(':refinery-store-query')
14 testImplementation libs.slf4j.simple 14 testImplementation libs.slf4j.simple
15 testImplementation libs.slf4j.log4j 15 testImplementation libs.slf4j.log4j
16} 16}
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 efc6146c..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;
4import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 4import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
5import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 5import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
6import tools.refinery.store.model.ModelStore; 6import tools.refinery.store.model.ModelStore;
7import tools.refinery.store.query.DNF; 7import tools.refinery.store.query.dnf.AnyQuery;
8import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.ModelQueryBuilder; 9import tools.refinery.store.query.ModelQueryBuilder;
9 10
10import java.util.Collection; 11import java.util.Collection;
@@ -23,25 +24,25 @@ public interface ViatraModelQueryBuilder extends ModelQueryBuilder {
23 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory); 24 ViatraModelQueryBuilder searchBackend(IQueryBackendFactory queryBackendFactory);
24 25
25 @Override 26 @Override
26 default ViatraModelQueryBuilder queries(DNF... queries) { 27 default ViatraModelQueryBuilder queries(AnyQuery... queries) {
27 ModelQueryBuilder.super.queries(queries); 28 ModelQueryBuilder.super.queries(queries);
28 return this; 29 return this;
29 } 30 }
30 31
31 @Override 32 @Override
32 default ViatraModelQueryBuilder queries(Collection<DNF> queries) { 33 default ViatraModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
33 ModelQueryBuilder.super.queries(queries); 34 ModelQueryBuilder.super.queries(queries);
34 return this; 35 return this;
35 } 36 }
36 37
37 @Override 38 @Override
38 ViatraModelQueryBuilder query(DNF query); 39 ViatraModelQueryBuilder query(AnyQuery query);
39 40
40 ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint); 41 ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint);
41 42
42 ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint); 43 ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint);
43 44
44 ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint); 45 ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint);
45 46
46 @Override 47 @Override
47 ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store); 48 ViatraModelQueryStoreAdapter createStoreAdapter(ModelStore store);
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 039f46fa..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,62 +7,92 @@ import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl;
7import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; 7import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 8import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
9import tools.refinery.store.model.Model; 9import tools.refinery.store.model.Model;
10import tools.refinery.store.query.DNF; 10import tools.refinery.store.model.ModelListener;
11import tools.refinery.store.query.AnyResultSet;
12import tools.refinery.store.query.EmptyResultSet;
11import tools.refinery.store.query.ResultSet; 13import tools.refinery.store.query.ResultSet;
14import tools.refinery.store.query.dnf.AnyQuery;
15import tools.refinery.store.query.dnf.FunctionalQuery;
16import tools.refinery.store.query.dnf.Query;
17import tools.refinery.store.query.dnf.RelationalQuery;
12import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; 18import tools.refinery.store.query.viatra.ViatraModelQueryAdapter;
19import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher;
20import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
21import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher;
13 22
14import java.lang.invoke.MethodHandle; 23import java.lang.invoke.MethodHandle;
15import java.lang.invoke.MethodHandles; 24import java.lang.invoke.MethodHandles;
16import java.util.Collection; 25import java.util.Collection;
17import java.util.Collections; 26import java.util.Collections;
18import java.util.HashMap; 27import java.util.LinkedHashMap;
19import java.util.Map; 28import java.util.Map;
20 29
21public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter { 30public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener {
22 private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; 31 private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery";
32 private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE;
23 private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; 33 private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends";
34 private static final MethodHandle GET_QUERY_BACKENDS_HANDLE;
24 35
25 private final Model model; 36 private final Model model;
26 private final ViatraModelQueryStoreAdapterImpl storeAdapter; 37 private final ViatraModelQueryStoreAdapterImpl storeAdapter;
27 private final ViatraQueryEngineImpl queryEngine; 38 private final ViatraQueryEngineImpl queryEngine;
28 private final MethodHandle setUpdatePropagationDelayedHandle; 39 private final Map<AnyQuery, AnyResultSet> resultSets;
29 private final MethodHandle getQueryBackendsHandle;
30 private final Map<DNF, ResultSet> resultSets;
31 private boolean pendingChanges; 40 private boolean pendingChanges;
32 41
33 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { 42 static {
34 this.model = model;
35 this.storeAdapter = storeAdapter;
36 var scope = new RelationalScope(this);
37 queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope);
38
39 try { 43 try {
40 var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); 44 var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup());
41 setUpdatePropagationDelayedHandle = lookup.findSetter(ViatraQueryEngineImpl.class, 45 SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class,
42 DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); 46 DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE);
43 getQueryBackendsHandle = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, 47 GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME,
44 Map.class); 48 Map.class);
45 } catch (IllegalAccessException | NoSuchFieldException e) { 49 } catch (IllegalAccessException | NoSuchFieldException e) {
46 throw new IllegalStateException("Cannot access private members of %s" 50 throw new IllegalStateException("Cannot access private members of %s"
47 .formatted(ViatraQueryEngineImpl.class.getName()), e); 51 .formatted(ViatraQueryEngineImpl.class.getName()), e);
48 } 52 }
53 }
54
55 ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) {
56 this.model = model;
57 this.storeAdapter = storeAdapter;
58 var scope = new RelationalScope(this);
59 queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope,
60 storeAdapter.getEngineOptions());
49 61
50 var querySpecifications = storeAdapter.getQuerySpecifications(); 62 var querySpecifications = storeAdapter.getQuerySpecifications();
51 GenericQueryGroup.of( 63 GenericQueryGroup.of(
52 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream() 64 Collections.<IQuerySpecification<?>>unmodifiableCollection(querySpecifications.values()).stream()
53 ).prepare(queryEngine); 65 ).prepare(queryEngine);
54 resultSets = new HashMap<>(querySpecifications.size()); 66 var vacuousQueries = storeAdapter.getVacuousQueries();
67 resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size());
55 for (var entry : querySpecifications.entrySet()) { 68 for (var entry : querySpecifications.entrySet()) {
56 var matcher = queryEngine.getMatcher(entry.getValue()); 69 var rawPatternMatcher = queryEngine.getMatcher(entry.getValue());
57 resultSets.put(entry.getKey(), matcher); 70 var query = entry.getKey();
71 resultSets.put(query, createResultSet((Query<?>) query, rawPatternMatcher));
72 }
73 for (var vacuousQuery : vacuousQueries) {
74 resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query<?>) vacuousQuery));
58 } 75 }
59 76
60 setUpdatePropagationDelayed(true); 77 setUpdatePropagationDelayed(true);
78 model.addListener(this);
79 }
80
81 private <T> ResultSet<T> createResultSet(Query<T> query, RawPatternMatcher matcher) {
82 if (query instanceof RelationalQuery relationalQuery) {
83 @SuppressWarnings("unchecked")
84 var resultSet = (ResultSet<T>) new RelationalViatraMatcher(this, relationalQuery, matcher);
85 return resultSet;
86 } else if (query instanceof FunctionalQuery<T> functionalQuery) {
87 return new FunctionalViatraMatcher<>(this, functionalQuery, matcher);
88 } else {
89 throw new IllegalArgumentException("Unknown query: " + query);
90 }
61 } 91 }
62 92
63 private void setUpdatePropagationDelayed(boolean value) { 93 private void setUpdatePropagationDelayed(boolean value) {
64 try { 94 try {
65 setUpdatePropagationDelayedHandle.invokeExact(queryEngine, value); 95 SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value);
66 } catch (Error e) { 96 } catch (Error e) {
67 // Fatal JVM errors should not be wrapped. 97 // Fatal JVM errors should not be wrapped.
68 throw e; 98 throw e;
@@ -74,7 +104,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
74 private Collection<IQueryBackend> getQueryBackends() { 104 private Collection<IQueryBackend> getQueryBackends() {
75 try { 105 try {
76 @SuppressWarnings("unchecked") 106 @SuppressWarnings("unchecked")
77 var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) getQueryBackendsHandle.invokeExact(queryEngine); 107 var backendMap = (Map<IQueryBackendFactory, IQueryBackend>) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine);
78 return backendMap.values(); 108 return backendMap.values();
79 } catch (Error e) { 109 } catch (Error e) {
80 // Fatal JVM errors should not be wrapped. 110 // Fatal JVM errors should not be wrapped.
@@ -95,12 +125,14 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
95 } 125 }
96 126
97 @Override 127 @Override
98 public ResultSet getResultSet(DNF query) { 128 public <T> ResultSet<T> getResultSet(Query<T> query) {
99 var resultSet = resultSets.get(query); 129 var resultSet = resultSets.get(query);
100 if (resultSet == null) { 130 if (resultSet == null) {
101 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name())); 131 throw new IllegalArgumentException("No matcher for query %s in model".formatted(query.name()));
102 } 132 }
103 return resultSet; 133 @SuppressWarnings("unchecked")
134 var typedResultSet = (ResultSet<T>) resultSet;
135 return typedResultSet;
104 } 136 }
105 137
106 @Override 138 @Override
@@ -132,4 +164,9 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter {
132 } 164 }
133 pendingChanges = false; 165 pendingChanges = false;
134 } 166 }
167
168 @Override
169 public void afterRestore() {
170 flushChanges();
171 }
135} 172}
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 9f1e55b1..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,34 +2,40 @@ package tools.refinery.store.query.viatra.internal;
2 2
3import org.eclipse.viatra.query.runtime.api.IQuerySpecification; 3import org.eclipse.viatra.query.runtime.api.IQuerySpecification;
4import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; 4import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchGenericBackendFactory; 5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions;
6import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; 6import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
7import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; 7import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
8import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory; 8import org.eclipse.viatra.query.runtime.rete.matcher.ReteBackendFactory;
9import tools.refinery.store.adapter.AbstractModelAdapterBuilder; 9import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
10import tools.refinery.store.model.ModelStore; 10import tools.refinery.store.model.ModelStore;
11import tools.refinery.store.model.ModelStoreBuilder; 11import tools.refinery.store.model.ModelStoreBuilder;
12import tools.refinery.store.query.DNF; 12import tools.refinery.store.query.dnf.AnyQuery;
13import tools.refinery.store.query.dnf.Dnf;
13import tools.refinery.store.query.viatra.ViatraModelQueryBuilder; 14import tools.refinery.store.query.viatra.ViatraModelQueryBuilder;
14import tools.refinery.store.query.viatra.internal.pquery.DNF2PQuery; 15import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction;
15import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 16import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory;
17import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
18import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery;
16 19
17import java.util.Collections; 20import java.util.*;
18import java.util.LinkedHashMap;
19import java.util.Map;
20import java.util.function.Function; 21import java.util.function.Function;
21 22
22public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder { 23public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder implements ViatraModelQueryBuilder {
23 private ViatraQueryEngineOptions.Builder engineOptionsBuilder; 24 private ViatraQueryEngineOptions.Builder engineOptionsBuilder;
24 private final DNF2PQuery dnf2PQuery = new DNF2PQuery(); 25 private QueryEvaluationHint defaultHint = new QueryEvaluationHint(Map.of(
25 private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>(); 26 // Use a cost function that ignores the initial (empty) model but allows higher arity input keys.
27 LocalSearchHintOptions.PLANNER_COST_FUNCTION, new FlatCostFunction()
28 ), (IQueryBackendFactory) null);
29 private final Dnf2PQuery dnf2PQuery = new Dnf2PQuery();
30 private final Set<AnyQuery> vacuousQueries = new LinkedHashSet<>();
31 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications = new LinkedHashMap<>();
26 32
27 public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) { 33 public ViatraModelQueryBuilderImpl(ModelStoreBuilder storeBuilder) {
28 super(storeBuilder); 34 super(storeBuilder);
29 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder() 35 engineOptionsBuilder = new ViatraQueryEngineOptions.Builder()
30 .withDefaultBackend(ReteBackendFactory.INSTANCE) 36 .withDefaultBackend(ReteBackendFactory.INSTANCE)
31 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE) 37 .withDefaultCachingBackend(ReteBackendFactory.INSTANCE)
32 .withDefaultSearchBackend(LocalSearchGenericBackendFactory.INSTANCE); 38 .withDefaultSearchBackend(RelationalLocalSearchBackendFactory.INSTANCE);
33 } 39 }
34 40
35 @Override 41 @Override
@@ -40,7 +46,7 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp
40 46
41 @Override 47 @Override
42 public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) { 48 public ViatraModelQueryBuilder defaultHint(QueryEvaluationHint queryEvaluationHint) {
43 engineOptionsBuilder.withDefaultHint(queryEvaluationHint); 49 defaultHint = defaultHint.overrideBy(queryEvaluationHint);
44 return this; 50 return this;
45 } 51 }
46 52
@@ -63,44 +69,68 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilder imp
63 } 69 }
64 70
65 @Override 71 @Override
66 public ViatraModelQueryBuilder query(DNF query) { 72 public ViatraModelQueryBuilder query(AnyQuery query) {
67 if (querySpecifications.containsKey(query)) { 73 if (querySpecifications.containsKey(query) || vacuousQueries.contains(query)) {
68 throw new IllegalArgumentException("%s was already added to the query engine".formatted(query.name())); 74 // Ignore duplicate queries.
75 return this;
76 }
77 var dnf = query.getDnf();
78 var reduction = dnf.getReduction();
79 switch (reduction) {
80 case NOT_REDUCIBLE -> {
81 var pQuery = dnf2PQuery.translate(dnf);
82 querySpecifications.put(query, pQuery.build());
83 }
84 case ALWAYS_FALSE -> vacuousQueries.add(query);
85 case ALWAYS_TRUE -> throw new IllegalArgumentException(
86 "Query %s is relationally unsafe (it matches every tuple)".formatted(query.name()));
87 default -> throw new IllegalArgumentException("Unknown reduction: " + reduction);
69 } 88 }
70 var pQuery = dnf2PQuery.translate(query);
71 querySpecifications.put(query, pQuery.build());
72 return this; 89 return this;
73 } 90 }
74 91
75 @Override 92 @Override
76 public ViatraModelQueryBuilder query(DNF query, QueryEvaluationHint queryEvaluationHint) { 93 public ViatraModelQueryBuilder query(AnyQuery query, QueryEvaluationHint queryEvaluationHint) {
94 hint(query.getDnf(), queryEvaluationHint);
77 query(query); 95 query(query);
78 hint(query, queryEvaluationHint);
79 return this; 96 return this;
80 } 97 }
81 98
82 @Override 99 @Override
83 public ViatraModelQueryBuilder computeHint(Function<DNF, QueryEvaluationHint> computeHint) { 100 public ViatraModelQueryBuilder computeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
84 dnf2PQuery.setComputeHint(computeHint); 101 dnf2PQuery.setComputeHint(computeHint);
85 return this; 102 return this;
86 } 103 }
87 104
88 @Override 105 @Override
89 public ViatraModelQueryBuilder hint(DNF dnf, QueryEvaluationHint queryEvaluationHint) { 106 public ViatraModelQueryBuilder hint(Dnf dnf, QueryEvaluationHint queryEvaluationHint) {
90 var pQuery = dnf2PQuery.getAlreadyTranslated(dnf); 107 dnf2PQuery.hint(dnf, queryEvaluationHint);
91 if (pQuery == null) {
92 throw new IllegalArgumentException(
93 "Cannot specify hint for %s, because it was not added to the query engine".formatted(dnf.name()));
94 }
95 pQuery.setEvaluationHints(pQuery.getEvaluationHints().overrideBy(queryEvaluationHint));
96 return this; 108 return this;
97 } 109 }
98 110
99 @Override 111 @Override
100 public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) { 112 public ViatraModelQueryStoreAdapterImpl createStoreAdapter(ModelStore store) {
101 validateSymbols(store); 113 validateSymbols(store);
102 return new ViatraModelQueryStoreAdapterImpl(store, engineOptionsBuilder.build(), dnf2PQuery.getRelationViews(), 114 dnf2PQuery.assertNoUnusedHints();
103 Collections.unmodifiableMap(querySpecifications)); 115 return new ViatraModelQueryStoreAdapterImpl(store, buildEngineOptions(), dnf2PQuery.getRelationViews(),
116 Collections.unmodifiableMap(querySpecifications), Collections.unmodifiableSet(vacuousQueries));
117 }
118
119 private ViatraQueryEngineOptions buildEngineOptions() {
120 // Workaround: manually override the default backend, because {@link ViatraQueryEngineOptions.Builder}
121 // ignores all backend requirements except {@code SPECIFIC}.
122 switch (defaultHint.getQueryBackendRequirementType()) {
123 case SPECIFIC -> engineOptionsBuilder.withDefaultBackend(defaultHint.getQueryBackendFactory());
124 case DEFAULT_CACHING -> engineOptionsBuilder.withDefaultBackend(
125 engineOptionsBuilder.build().getDefaultCachingBackendFactory());
126 case DEFAULT_SEARCH -> engineOptionsBuilder.withDefaultBackend(
127 engineOptionsBuilder.build().getDefaultSearchBackendFactory());
128 case UNSPECIFIED -> {
129 // Nothing to do, leave the default backend unchanged.
130 }
131 }
132 engineOptionsBuilder.withDefaultHint(defaultHint);
133 return engineOptionsBuilder.build();
104 } 134 }
105 135
106 private void validateSymbols(ModelStore store) { 136 private void validateSymbols(ModelStore store) {
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 394e407e..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,27 +5,34 @@ import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions;
5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
6import tools.refinery.store.model.Model; 6import tools.refinery.store.model.Model;
7import tools.refinery.store.model.ModelStore; 7import tools.refinery.store.model.ModelStore;
8import tools.refinery.store.query.DNF; 8import tools.refinery.store.query.dnf.AnyQuery;
9import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter; 9import tools.refinery.store.query.viatra.ViatraModelQueryStoreAdapter;
10import tools.refinery.store.query.viatra.internal.pquery.RawPatternMatcher; 10import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
11import tools.refinery.store.query.view.AnyRelationView; 11import tools.refinery.store.query.view.AnyRelationView;
12 12
13import java.util.Collection; 13import java.util.*;
14import java.util.Map;
15 14
16public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter { 15public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAdapter {
17 private final ModelStore store; 16 private final ModelStore store;
18 private final ViatraQueryEngineOptions engineOptions; 17 private final ViatraQueryEngineOptions engineOptions;
19 private final Map<AnyRelationView, IInputKey> inputKeys; 18 private final Map<AnyRelationView, IInputKey> inputKeys;
20 private final Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications; 19 private final Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications;
20 private final Set<AnyQuery> vacuousQueries;
21 private final Set<AnyQuery> allQueries;
21 22
22 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions, 23 ViatraModelQueryStoreAdapterImpl(ModelStore store, ViatraQueryEngineOptions engineOptions,
23 Map<AnyRelationView, IInputKey> inputKeys, 24 Map<AnyRelationView, IInputKey> inputKeys,
24 Map<DNF, IQuerySpecification<RawPatternMatcher>> querySpecifications) { 25 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> querySpecifications,
26 Set<AnyQuery> vacuousQueries) {
25 this.store = store; 27 this.store = store;
26 this.engineOptions = engineOptions; 28 this.engineOptions = engineOptions;
27 this.inputKeys = inputKeys; 29 this.inputKeys = inputKeys;
28 this.querySpecifications = querySpecifications; 30 this.querySpecifications = querySpecifications;
31 this.vacuousQueries = vacuousQueries;
32 var mutableAllQueries = new LinkedHashSet<AnyQuery>(querySpecifications.size() + vacuousQueries.size());
33 mutableAllQueries.addAll(querySpecifications.keySet());
34 mutableAllQueries.addAll(vacuousQueries);
35 this.allQueries = Collections.unmodifiableSet(mutableAllQueries);
29 } 36 }
30 37
31 @Override 38 @Override
@@ -42,14 +49,18 @@ public class ViatraModelQueryStoreAdapterImpl implements ViatraModelQueryStoreAd
42 } 49 }
43 50
44 @Override 51 @Override
45 public Collection<DNF> getQueries() { 52 public Collection<AnyQuery> getQueries() {
46 return querySpecifications.keySet(); 53 return allQueries;
47 } 54 }
48 55
49 Map<DNF, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() { 56 Map<AnyQuery, IQuerySpecification<RawPatternMatcher>> getQuerySpecifications() {
50 return querySpecifications; 57 return querySpecifications;
51 } 58 }
52 59
60 Set<AnyQuery> getVacuousQueries() {
61 return vacuousQueries;
62 }
63
53 @Override 64 @Override
54 public ViatraQueryEngineOptions getEngineOptions() { 65 public ViatraQueryEngineOptions getEngineOptions() {
55 return engineOptions; 66 return engineOptions;
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;
3import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; 3import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; 4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; 5import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication;
6import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey;
7import tools.refinery.store.query.term.DataSort;
6import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper; 8import tools.refinery.store.query.viatra.internal.pquery.RelationViewWrapper;
7import tools.refinery.store.query.view.AnyRelationView; 9import tools.refinery.store.query.view.AnyRelationView;
8 10
@@ -37,6 +39,9 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
37 39
38 @Override 40 @Override
39 public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) { 41 public Collection<InputKeyImplication> getImplications(IInputKey implyingKey) {
42 if (implyingKey instanceof JavaTransitiveInstancesKey) {
43 return List.of();
44 }
40 var relationView = checkKey(implyingKey); 45 var relationView = checkKey(implyingKey);
41 var relationViewImplications = relationView.getImpliedRelationViews(); 46 var relationViewImplications = relationView.getImpliedRelationViews();
42 var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size()); 47 var inputKeyImplications = new HashSet<InputKeyImplication>(relationViewImplications.size());
@@ -52,11 +57,25 @@ public class RelationalQueryMetaContext extends AbstractQueryMetaContext {
52 relationViewImplication.impliedIndices())); 57 relationViewImplication.impliedIndices()));
53 } 58 }
54 } 59 }
60 var sorts = relationView.getSorts();
61 int arity = relationView.arity();
62 for (int i = 0; i < arity; i++) {
63 var sort = sorts.get(i);
64 if (sort instanceof DataSort<?> dataSort) {
65 var javaTransitiveInstancesKey = new JavaTransitiveInstancesKey(dataSort.type());
66 var javaImplication = new InputKeyImplication(implyingKey, javaTransitiveInstancesKey,
67 List.of(i));
68 inputKeyImplications.add(javaImplication);
69 }
70 }
55 return inputKeyImplications; 71 return inputKeyImplications;
56 } 72 }
57 73
58 @Override 74 @Override
59 public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) { 75 public Map<Set<Integer>, Set<Integer>> getFunctionalDependencies(IInputKey key) {
76 if (key instanceof JavaTransitiveInstancesKey) {
77 return Map.of();
78 }
60 var relationView = checkKey(key); 79 var relationView = checkKey(key);
61 var functionalDependencies = relationView.getFunctionalDependencies(); 80 var functionalDependencies = relationView.getFunctionalDependencies();
62 var flattened = new HashMap<Set<Integer>, Set<Integer>>(functionalDependencies.size()); 81 var flattened = new HashMap<Set<Integer>, Set<Integer>>(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 {
54 54
55 @Override 55 @Override
56 public boolean isIndexed(IInputKey key, IndexingService service) { 56 public boolean isIndexed(IInputKey key, IndexingService service) {
57 if (key instanceof AnyRelationView relationalKey) { 57 if (key instanceof RelationViewWrapper wrapper) {
58 var relationalKey = wrapper.getWrappedKey();
58 return this.modelUpdateListener.containsRelationView(relationalKey); 59 return this.modelUpdateListener.containsRelationView(relationalKey);
59 } else { 60 } else {
60 return false; 61 return false;
@@ -83,10 +84,7 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
83 84
84 @Override 85 @Override
85 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { 86 public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
86 var relationViewKey = checkKey(key); 87 Iterator<Object[]> iterator = enumerate(key, seedMask, seed).iterator();
87 Iterable<Object[]> allObjects = relationViewKey.getAll(model);
88 Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
89 Iterator<Object[]> iterator = filteredBySeed.iterator();
90 int result = 0; 88 int result = 0;
91 while (iterator.hasNext()) { 89 while (iterator.hasNext()) {
92 iterator.next(); 90 iterator.next();
@@ -102,13 +100,25 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
102 100
103 @Override 101 @Override
104 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { 102 public Iterable<Tuple> enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) {
103 var filteredBySeed = enumerate(key, seedMask, seed);
104 return map(filteredBySeed, Tuples::flatTupleOf);
105 }
106
107 @Override
108 public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) {
109 var index = seedMask.getFirstOmittedIndex().orElseThrow(
110 () -> new IllegalArgumentException("Seed mask does not omit a value"));
111 var filteredBySeed = enumerate(key, seedMask, seed);
112 return map(filteredBySeed, array -> array[index]);
113 }
114
115 private Iterable<Object[]> enumerate(IInputKey key, TupleMask seedMask, ITuple seed) {
105 var relationViewKey = checkKey(key); 116 var relationViewKey = checkKey(key);
106 Iterable<Object[]> allObjects = relationViewKey.getAll(model); 117 Iterable<Object[]> allObjects = relationViewKey.getAll(model);
107 Iterable<Object[]> filteredBySeed = filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed)); 118 return filter(allObjects, objectArray -> isMatching(objectArray, seedMask, seed));
108 return map(filteredBySeed, Tuples::flatTupleOf);
109 } 119 }
110 120
111 private boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) { 121 private static boolean isMatching(Object[] tuple, TupleMask seedMask, ITuple seed) {
112 for (int i = 0; i < seedMask.indices.length; i++) { 122 for (int i = 0; i < seedMask.indices.length; i++) {
113 final Object seedElement = seed.get(i); 123 final Object seedElement = seed.get(i);
114 final Object tupleElement = tuple[seedMask.indices[i]]; 124 final Object tupleElement = tuple[seedMask.indices[i]];
@@ -120,11 +130,6 @@ public class RelationalRuntimeContext implements IQueryRuntimeContext {
120 } 130 }
121 131
122 @Override 132 @Override
123 public Iterable<?> enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) {
124 return enumerateTuples(key, seedMask, seed);
125 }
126
127 @Override
128 public boolean containsTuple(IInputKey key, ITuple seed) { 133 public boolean containsTuple(IInputKey key, ITuple seed) {
129 var relationViewKey = checkKey(key); 134 var relationViewKey = checkKey(key);
130 return relationViewKey.get(model, seed.getElements()); 135 return relationViewKey.get(model, seed.getElements());
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 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 * SPDX-License-Identifier: EPL-2.0
7 *******************************************************************************/
8package tools.refinery.store.query.viatra.internal.localsearch;
9
10import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
11import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
12import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor;
13
14import java.util.Iterator;
15
16/**
17 * An operation that can be used to enumerate all possible values for a single position based on a constraint
18 * @author Zoltan Ujhelyi, Akos Horvath
19 * @since 2.0
20 */
21abstract class ExtendOperationExecutor<T> implements ISearchOperationExecutor {
22
23 private Iterator<? extends T> it;
24
25 /**
26 * Returns an iterator with the possible options from the current state
27 * @since 2.0
28 */
29 @SuppressWarnings("squid:S1452")
30 protected abstract Iterator<? extends T> getIterator(MatchingFrame frame, ISearchContext context);
31 /**
32 * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}.
33 *
34 * @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.
35 * @since 2.0
36 */
37 protected abstract boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context);
38
39 /**
40 * Restores the frame to the state before {@link #fillInValue(Object, MatchingFrame, ISearchContext)}. Called during
41 * {@link #onBacktrack(MatchingFrame, ISearchContext)}.
42 *
43 * @since 2.0
44 */
45 protected abstract void cleanup(MatchingFrame frame, ISearchContext context);
46
47 @Override
48 public void onInitialize(MatchingFrame frame, ISearchContext context) {
49 it = getIterator(frame, context);
50 }
51
52 @Override
53 public void onBacktrack(MatchingFrame frame, ISearchContext context) {
54 it = null;
55
56 }
57
58 /**
59 * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute}
60 * that handles failed unification of variables correctly.
61 * @param frame The matching frame to extend.
62 * @param context The search context.
63 * @return {@code true} if an extension was found, {@code false} otherwise.
64 */
65 @Override
66 public boolean execute(MatchingFrame frame, ISearchContext context) {
67 while (it.hasNext()) {
68 var newValue = it.next();
69 if (fillInValue(newValue, frame, context)) {
70 return true;
71 }
72 }
73 return false;
74 }
75}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 * SPDX-License-Identifier: EPL-2.0
7 *******************************************************************************/
8package tools.refinery.store.query.viatra.internal.localsearch;
9
10import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
11import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
12import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation;
13import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
14import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
15import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
16import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
17import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
18import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple;
19
20import java.util.Iterator;
21import java.util.List;
22import java.util.function.Function;
23
24/**
25 * @author Grill Balázs
26 * @since 1.4
27 *
28 */
29public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatcherOperation {
30
31 private class Executor extends ExtendOperationExecutor<Tuple> {
32 private final VolatileModifiableMaskedTuple maskedTuple;
33
34 public Executor() {
35 maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask());
36 }
37
38 @Override
39 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
40 maskedTuple.updateTuple(frame);
41 IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment());
42 return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator();
43 }
44
45 /**
46 * @since 2.0
47 */
48 @Override
49 protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) {
50 TupleMask mask = information.getFullFrameMask();
51 // The first loop clears out the elements from a possible previous iteration
52 for(int i : information.getFreeParameterIndices()) {
53 mask.set(frame, i, null);
54 }
55 for(int i : information.getFreeParameterIndices()) {
56 Object oldValue = mask.getValue(frame, i);
57 Object valueToFill = result.get(i);
58 if (oldValue != null && !oldValue.equals(valueToFill)){
59 // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller.
60 // In this case if the callee assigns different values the frame shall be dropped
61 return false;
62 }
63 mask.set(frame, i, valueToFill);
64 }
65 return true;
66 }
67
68 @Override
69 protected void cleanup(MatchingFrame frame, ISearchContext context) {
70 TupleMask mask = information.getFullFrameMask();
71 for(int i : information.getFreeParameterIndices()){
72 mask.set(frame, i, null);
73 }
74
75 }
76
77 @Override
78 public ISearchOperation getOperation() {
79 return ExtendPositivePatternCall.this;
80 }
81 }
82
83 private final CallInformation information;
84
85 /**
86 * @since 1.7
87 */
88 public ExtendPositivePatternCall(CallInformation information) {
89 this.information = information;
90 }
91
92 @Override
93 public ISearchOperationExecutor createExecutor() {
94 return new Executor();
95 }
96
97 @Override
98 public List<Integer> getVariablePositions() {
99 return information.getVariablePositions();
100 }
101
102 @Override
103 public String toString() {
104 return toString(Object::toString);
105 }
106
107 @Override
108 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) {
109 return "extend find " + information.toString(variableMapping);
110 }
111
112 @Override
113 public CallInformation getCallInformation() {
114 return information;
115 }
116}
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 @@
1package tools.refinery.store.query.viatra.internal.localsearch;
2
3import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
4import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction;
5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
6import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
7import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
8import org.eclipse.viatra.query.runtime.matchers.util.Accuracy;
9
10import java.util.Optional;
11
12public class FlatCostFunction extends StatisticsBasedConstraintCostFunction {
13 public FlatCostFunction() {
14 // No inverse navigation penalty thanks to relational storage.
15 super(0);
16 }
17
18 @Override
19 public Optional<Long> projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) {
20 // We always start from an empty model, where every projection is of size 0.
21 // Therefore, projection size estimation is meaningless.
22 return Optional.empty();
23 }
24
25 @Override
26 protected double _calculateCost(TypeConstraint constraint, IConstraintEvaluationContext input) {
27 // Assume a flat cost for each relation. Maybe adjust in the future if we perform indexing?
28 return DEFAULT_COST;
29 }
30}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 * SPDX-License-Identifier: EPL-2.0
7 *******************************************************************************/
8package tools.refinery.store.query.viatra.internal.localsearch;
9
10import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame;
11import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext;
12import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation;
13import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation;
14import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
15import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
16import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
17import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple;
18import org.eclipse.viatra.query.runtime.matchers.util.Preconditions;
19
20import java.util.*;
21import java.util.function.Function;
22import java.util.stream.Collectors;
23
24/**
25 * @author Zoltan Ujhelyi
26 * @since 1.7
27 */
28public class GenericTypeExtend implements IIteratingSearchOperation {
29 private class Executor extends ExtendOperationExecutor<Tuple> {
30 private final VolatileMaskedTuple maskedTuple;
31
32 public Executor() {
33 this.maskedTuple = new VolatileMaskedTuple(callMask);
34 }
35
36 @Override
37 protected Iterator<? extends Tuple> getIterator(MatchingFrame frame, ISearchContext context) {
38 maskedTuple.updateTuple(frame);
39 return context.getRuntimeContext().enumerateTuples(type, indexerMask, maskedTuple).iterator();
40 }
41
42 @Override
43 protected boolean fillInValue(Tuple newTuple, MatchingFrame frame, ISearchContext context) {
44 for (Integer position : unboundVariableIndices) {
45 frame.setValue(position, null);
46 }
47 for (int i = 0; i < positions.length; i++) {
48 Object newValue = newTuple.get(i);
49 Object oldValue = frame.getValue(positions[i]);
50 if (oldValue != null && !Objects.equals(oldValue, newValue)) {
51 // If positions tuple maps more than one values for the same element (e.g. loop), it means that
52 // these arguments are to unified by the caller. In this case if the callee assigns different values
53 // the frame shall be considered a failed match
54 return false;
55 }
56 frame.setValue(positions[i], newValue);
57 }
58 return true;
59 }
60
61 @Override
62 protected void cleanup(MatchingFrame frame, ISearchContext context) {
63 for (Integer position : unboundVariableIndices) {
64 frame.setValue(position, null);
65 }
66 }
67
68 @Override
69 public ISearchOperation getOperation() {
70 return GenericTypeExtend.this;
71 }
72 }
73
74 private final IInputKey type;
75 private final int[] positions;
76 private final List<Integer> positionList;
77 private final Set<Integer> unboundVariableIndices;
78 private final TupleMask indexerMask;
79 private final TupleMask callMask;
80
81 /**
82 *
83 * @param type
84 * the type to execute the extend operation on
85 * @param positions
86 * the parameter positions that represent the variables of the input key
87 * @param unboundVariableIndices
88 * the set of positions that are bound at the start of the operation
89 */
90 public GenericTypeExtend(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, Set<Integer> unboundVariableIndices) {
91 Preconditions.checkArgument(positions.length == type.getArity(),
92 "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(),
93 type.getArity(), positions.length);
94 List<Integer> modifiablePositionList = new ArrayList<>();
95 for (int position : positions) {
96 modifiablePositionList.add(position);
97 }
98 this.positionList = Collections.unmodifiableList(modifiablePositionList);
99 this.positions = positions;
100 this.type = type;
101
102 this.unboundVariableIndices = unboundVariableIndices;
103 this.indexerMask = indexerMask;
104 this.callMask = callMask;
105 }
106
107 @Override
108 public IInputKey getIteratedInputKey() {
109 return type;
110 }
111
112 @Override
113 public ISearchOperationExecutor createExecutor() {
114 return new Executor();
115 }
116
117 @Override
118 public List<Integer> getVariablePositions() {
119 return positionList;
120 }
121
122 @Override
123 public String toString() {
124 return toString(Object::toString);
125 }
126
127 @Override
128 public String toString(@SuppressWarnings("squid:S4276") Function<Integer, String> variableMapping) {
129 return "extend " + type.getPrettyPrintableName() + "("
130 + positionList.stream()
131 .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input)))
132 .collect(Collectors.joining(", "))
133 + ")";
134 }
135
136}
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 @@
1package tools.refinery.store.query.viatra.internal.localsearch;
2
3import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
4import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
6import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
7import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider;
8import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability;
9import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend;
10import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
11import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
12import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
13import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
14
15public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory {
16 public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory();
17
18 private RelationalLocalSearchBackendFactory() {
19 }
20
21 @Override
22 public IQueryBackend create(IQueryBackendContext context) {
23 return new LocalSearchBackend(context) {
24 // Create a new {@link IPlanProvider}, because the original {@link LocalSearchBackend#planProvider} is not
25 // accessible.
26 private final IPlanProvider planProvider = new SimplePlanProvider(context.getLogger());
27
28 @Override
29 protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query,
30 QueryEvaluationHint hints) {
31 return new RelationalLocalSearchResultProvider(this, context, query, planProvider, hints);
32 }
33
34 @Override
35 public IQueryBackendFactory getFactory() {
36 return RelationalLocalSearchBackendFactory.this;
37 }
38 };
39 }
40
41 @Override
42 public Class<? extends IQueryBackend> getBackendClass() {
43 return LocalSearchBackend.class;
44 }
45
46 @Override
47 public IMatcherCapability calculateRequiredCapability(PQuery pQuery, QueryEvaluationHint queryEvaluationHint) {
48 return LocalSearchHints.parse(queryEvaluationHint);
49 }
50
51 @Override
52 public boolean isCaching() {
53 return false;
54 }
55}
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 @@
1package tools.refinery.store.query.viatra.internal.localsearch;
2
3import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider;
4import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend;
5import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints;
6import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider;
7import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler;
8import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
9import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
11
12class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider {
13 public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
14 IPlanProvider planProvider, QueryEvaluationHint userHints) {
15 super(backend, context, query, planProvider, userHints);
16 }
17
18 @Override
19 protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext,
20 LocalSearchHints configuration) {
21 return new RelationalOperationCompiler(runtimeContext);
22 }
23}
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 @@
1package tools.refinery.store.query.viatra.internal.localsearch;
2
3import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue;
4import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation;
5import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler;
6import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
7import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext;
8import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
9import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
10import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
11import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
12import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
13
14import java.util.*;
15
16public class RelationalOperationCompiler extends GenericOperationCompiler {
17 public RelationalOperationCompiler(IQueryRuntimeContext runtimeContext) {
18 super(runtimeContext);
19 }
20
21 @Override
22 protected void createExtend(TypeConstraint typeConstraint, Map<PVariable, Integer> variableMapping) {
23 IInputKey inputKey = typeConstraint.getSupplierKey();
24 Tuple tuple = typeConstraint.getVariablesTuple();
25
26 int[] positions = new int[tuple.getSize()];
27 List<Integer> boundVariableIndices = new ArrayList<>();
28 List<Integer> boundVariables = new ArrayList<>();
29 Set<Integer> unboundVariables = new HashSet<>();
30 for (int i = 0; i < tuple.getSize(); i++) {
31 PVariable variable = (PVariable) tuple.get(i);
32 Integer position = variableMapping.get(variable);
33 positions[i] = position;
34 if (variableBindings.get(typeConstraint).contains(position)) {
35 boundVariableIndices.add(i);
36 boundVariables.add(position);
37 } else {
38 unboundVariables.add(position);
39 }
40 }
41 TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices);
42 TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables);
43 // If multiple tuple elements from the indexer should be bound to the same variable, we must use a
44 // {@link GenericTypeExtend} check whether the tuple elements have the same value.
45 if (unboundVariables.size() == 1 && indexerMask.getSize() + 1 == indexerMask.getSourceWidth()) {
46 operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask,
47 unboundVariables.iterator().next()));
48 } else {
49 // Use a fixed version of
50 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles
51 // failed unification of variables correctly.
52 operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables));
53 }
54 }
55
56 @Override
57 protected void createExtend(PositivePatternCall pCall, Map<PVariable, Integer> variableMapping) {
58 CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall));
59 // Use a fixed version of
60 // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles
61 // failed unification of variables correctly.
62 operations.add(new ExtendPositivePatternCall(information));
63 dependencies.add(information.getCallWithAdornment());
64 }
65}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
4import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
5import tools.refinery.store.map.Cursor;
6import tools.refinery.store.tuple.TupleLike;
7
8import java.util.Iterator;
9
10class FunctionalCursor<T> implements Cursor<TupleLike, T> {
11 private final IterableIndexer indexer;
12 private final Iterator<Tuple> iterator;
13 private boolean terminated;
14 private TupleLike key;
15 private T value;
16
17 public FunctionalCursor(IterableIndexer indexer) {
18 this.indexer = indexer;
19 iterator = indexer.getSignatures().iterator();
20 }
21
22 @Override
23 public TupleLike getKey() {
24 return key;
25 }
26
27 @Override
28 public T getValue() {
29 return value;
30 }
31
32 @Override
33 public boolean isTerminated() {
34 return terminated;
35 }
36
37 @Override
38 public boolean move() {
39 if (!terminated && iterator.hasNext()) {
40 var match = iterator.next();
41 key = new ViatraTupleLike(match);
42 value = MatcherUtils.getSingleValue(indexer.get(match));
43 return true;
44 }
45 terminated = true;
46 return false;
47 }
48}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
4import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
5import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
6import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer;
7import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.query.ModelQueryAdapter;
10import tools.refinery.store.query.ResultSet;
11import tools.refinery.store.query.dnf.FunctionalQuery;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
14import tools.refinery.store.tuple.TupleLike;
15
16/**
17 * Directly access the tuples inside a VIATRA pattern matcher.<p>
18 * This class neglects calling
19 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
20 * and
21 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)},
22 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
23 * implementation for these methods.
24 * Using this class with any other runtime context may lead to undefined behavior.
25 */
26public class FunctionalViatraMatcher<T> implements ResultSet<T> {
27 private final ViatraModelQueryAdapterImpl adapter;
28 private final FunctionalQuery<T> query;
29 private final TupleMask emptyMask;
30 private final TupleMask omitOutputMask;
31 private final IQueryResultProvider backend;
32 private final IterableIndexer omitOutputIndexer;
33
34 public FunctionalViatraMatcher(ViatraModelQueryAdapterImpl adapter, FunctionalQuery<T> query,
35 RawPatternMatcher rawPatternMatcher) {
36 this.adapter = adapter;
37 this.query = query;
38 int arity = query.arity();
39 int arityWithOutput = arity + 1;
40 emptyMask = TupleMask.empty(arityWithOutput);
41 omitOutputMask = TupleMask.omit(arity, arityWithOutput);
42 backend = rawPatternMatcher.getBackend();
43 if (backend instanceof RetePatternMatcher reteBackend) {
44 var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask);
45 if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) {
46 omitOutputIndexer = iterableOmitOutputIndexer;
47 } else {
48 omitOutputIndexer = null;
49 }
50 } else {
51 omitOutputIndexer = null;
52 }
53 }
54
55 @Override
56 public ModelQueryAdapter getAdapter() {
57 return adapter;
58 }
59
60 @Override
61 public Query<T> getQuery() {
62 return query;
63 }
64
65 @Override
66 public T get(TupleLike parameters) {
67 var tuple = MatcherUtils.toViatraTuple(parameters);
68 if (omitOutputIndexer == null) {
69 return MatcherUtils.getSingleValue(backend.getAllMatches(omitOutputMask, tuple).iterator());
70 } else {
71 return MatcherUtils.getSingleValue(omitOutputIndexer.get(tuple));
72 }
73 }
74
75 @Override
76 public Cursor<TupleLike, T> getAll() {
77 if (omitOutputIndexer == null) {
78 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
79 return new UnsafeFunctionalCursor<>(allMatches.iterator());
80 }
81 return new FunctionalCursor<>(omitOutputIndexer);
82 }
83
84 @Override
85 public int size() {
86 if (omitOutputIndexer == null) {
87 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
88 }
89 return omitOutputIndexer.getBucketCount();
90 }
91}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
4import org.eclipse.viatra.query.runtime.rete.index.Indexer;
5import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine;
6import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
7import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo;
8
9import java.lang.invoke.MethodHandle;
10import java.lang.invoke.MethodHandles;
11import java.lang.invoke.MethodType;
12
13final class IndexerUtils {
14 private static final MethodHandle GET_ENGINE_HANDLE;
15 private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE;
16 private static final MethodHandle ACCESS_PROJECTION_HANDLE;
17
18 static {
19 try {
20 var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup());
21 GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class);
22 GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace",
23 RecipeTraceInfo.class);
24 ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection",
25 MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class));
26 } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) {
27 throw new IllegalStateException("Cannot access private members of %s"
28 .formatted(RetePatternMatcher.class.getPackageName()), e);
29 }
30 }
31
32 private IndexerUtils() {
33 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
34 }
35
36 public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) {
37 try {
38 var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend);
39 var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend);
40 return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask);
41 } catch (Error e) {
42 // Fatal JVM errors should not be wrapped.
43 throw e;
44 } catch (Throwable e) {
45 throw new IllegalStateException("Cannot access matcher for mask " + mask, e);
46 }
47 }
48}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
5import org.jetbrains.annotations.Nullable;
6import tools.refinery.store.tuple.Tuple;
7import tools.refinery.store.tuple.TupleLike;
8
9import java.util.Iterator;
10
11final class MatcherUtils {
12 private MatcherUtils() {
13 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
14 }
15
16 public static org.eclipse.viatra.query.runtime.matchers.tuple.Tuple toViatraTuple(TupleLike tuple) {
17 if (tuple instanceof ViatraTupleLike viatraTupleLike) {
18 return viatraTupleLike.wrappedTuple().toImmutable();
19 }
20 int size = tuple.getSize();
21 var array = new Object[size];
22 for (int i = 0; i < size; i++) {
23 var value = tuple.get(i);
24 array[i] = Tuple.of(value);
25 }
26 return Tuples.flatTupleOf(array);
27 }
28
29
30 public static <T> T getSingleValue(@Nullable Iterable<? extends ITuple> tuples) {
31 if (tuples == null) {
32 return null;
33 }
34 return getSingleValue(tuples.iterator());
35 }
36
37 public static <T> T getSingleValue(Iterator<? extends ITuple> iterator) {
38 if (!iterator.hasNext()) {
39 return null;
40 }
41 var match = iterator.next();
42 @SuppressWarnings("unchecked")
43 var result = (T) match.get(match.getSize() - 1);
44 if (iterator.hasNext()) {
45 var input = new OmitOutputViatraTupleLike(match);
46 throw new IllegalStateException("Query is not functional for input tuple: " + input);
47 }
48 return result;
49 }
50}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.tuple.Tuple1;
5import tools.refinery.store.tuple.TupleLike;
6
7record OmitOutputViatraTupleLike(ITuple wrappedTuple) implements TupleLike {
8 @Override
9 public int getSize() {
10 return wrappedTuple.getSize() - 1;
11 }
12
13 @Override
14 public int get(int element) {
15 var wrappedValue = (Tuple1) wrappedTuple.get(element);
16 return wrappedValue.value0();
17 }
18
19 @Override
20 public String toString() {
21 return TupleLike.toString(this);
22 }
23}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
4import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
5import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
6
7public class RawPatternMatcher extends GenericPatternMatcher {
8 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
9 super(specification);
10 }
11
12 IQueryResultProvider getBackend() {
13 return backend;
14 }
15}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.map.Cursor;
5import tools.refinery.store.tuple.TupleLike;
6
7import java.util.Iterator;
8
9class RelationalCursor implements Cursor<TupleLike, Boolean> {
10 private final Iterator<? extends ITuple> tuplesIterator;
11 private boolean terminated;
12 private TupleLike key;
13
14 public RelationalCursor(Iterator<? extends ITuple> tuplesIterator) {
15 this.tuplesIterator = tuplesIterator;
16 }
17
18 @Override
19 public TupleLike getKey() {
20 return key;
21 }
22
23 @Override
24 public Boolean getValue() {
25 return true;
26 }
27
28 @Override
29 public boolean isTerminated() {
30 return terminated;
31 }
32
33 @Override
34 public boolean move() {
35 if (!terminated && tuplesIterator.hasNext()) {
36 key = new ViatraTupleLike(tuplesIterator.next());
37 return true;
38 }
39 terminated = true;
40 return false;
41 }
42}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider;
4import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask;
5import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
6import org.eclipse.viatra.query.runtime.rete.index.Indexer;
7import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher;
8import tools.refinery.store.map.Cursor;
9import tools.refinery.store.map.Cursors;
10import tools.refinery.store.query.ModelQueryAdapter;
11import tools.refinery.store.query.ResultSet;
12import tools.refinery.store.query.dnf.Query;
13import tools.refinery.store.query.dnf.RelationalQuery;
14import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
15import tools.refinery.store.tuple.TupleLike;
16
17/**
18 * Directly access the tuples inside a VIATRA pattern matcher.<p>
19 * This class neglects calling
20 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}
21 * and
22 * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)},
23 * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial
24 * implementation for these methods.
25 * Using this class with any other runtime context may lead to undefined behavior.
26 */
27public class RelationalViatraMatcher implements ResultSet<Boolean> {
28 private final ViatraModelQueryAdapterImpl adapter;
29 private final RelationalQuery query;
30 private final TupleMask emptyMask;
31 private final TupleMask identityMask;
32 private final IQueryResultProvider backend;
33 private final Indexer emptyMaskIndexer;
34
35 public RelationalViatraMatcher(ViatraModelQueryAdapterImpl adapter, RelationalQuery query,
36 RawPatternMatcher rawPatternMatcher) {
37 this.adapter = adapter;
38 this.query = query;
39 int arity = query.arity();
40 emptyMask = TupleMask.empty(arity);
41 identityMask = TupleMask.identity(arity);
42 backend = rawPatternMatcher.getBackend();
43 if (backend instanceof RetePatternMatcher reteBackend) {
44 emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask);
45 } else {
46 emptyMaskIndexer = null;
47 }
48 }
49
50 @Override
51 public ModelQueryAdapter getAdapter() {
52 return adapter;
53 }
54
55 @Override
56 public Query<Boolean> getQuery() {
57 return query;
58 }
59
60 @Override
61 public Boolean get(TupleLike parameters) {
62 var tuple = MatcherUtils.toViatraTuple(parameters);
63 if (emptyMaskIndexer == null) {
64 return backend.hasMatch(identityMask, tuple);
65 }
66 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
67 return matches != null && matches.contains(tuple);
68 }
69
70 @Override
71 public Cursor<TupleLike, Boolean> getAll() {
72 if (emptyMaskIndexer == null) {
73 var allMatches = backend.getAllMatches(emptyMask, Tuples.staticArityFlatTupleOf());
74 return new RelationalCursor(allMatches.iterator());
75 }
76 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
77 return matches == null ? Cursors.empty() : new RelationalCursor(matches.stream().iterator());
78 }
79
80 @Override
81 public int size() {
82 if (emptyMaskIndexer == null) {
83 return backend.countMatches(emptyMask, Tuples.staticArityFlatTupleOf());
84 }
85 var matches = emptyMaskIndexer.get(Tuples.staticArityFlatTupleOf());
86 return matches == null ? 0 : matches.size();
87 }
88
89}
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 @@
1package tools.refinery.store.query.viatra.internal.matcher;
2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.map.Cursor;
5import tools.refinery.store.tuple.TupleLike;
6
7import java.util.Iterator;
8
9/**
10 * Cursor for a functional result set that iterates over a stream of raw matches and doesn't check whether the
11 * functional dependency of the output on the inputs is obeyed.
12 * @param <T> The output type.
13 */
14class UnsafeFunctionalCursor<T> implements Cursor<TupleLike, T> {
15 private final Iterator<? extends ITuple> tuplesIterator;
16 private boolean terminated;
17 private TupleLike key;
18 private T value;
19
20 public UnsafeFunctionalCursor(Iterator<? extends ITuple> tuplesIterator) {
21 this.tuplesIterator = tuplesIterator;
22 }
23
24 @Override
25 public TupleLike getKey() {
26 return key;
27 }
28
29 @Override
30 public T getValue() {
31 return value;
32 }
33
34 @Override
35 public boolean isTerminated() {
36 return terminated;
37 }
38
39 @Override
40 public boolean move() {
41 if (!terminated && tuplesIterator.hasNext()) {
42 var match = tuplesIterator.next();
43 key = new OmitOutputViatraTupleLike(match);
44 @SuppressWarnings("unchecked")
45 var typedValue = (T) match.get(match.getSize() - 1);
46 value = typedValue;
47 return true;
48 }
49 terminated = true;
50 return false;
51 }
52}
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/internal/matcher/ViatraTupleLike.java
index 46c28434..76a3e40b 100644
--- 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/internal/matcher/ViatraTupleLike.java
@@ -1,10 +1,10 @@
1package tools.refinery.store.query.viatra; 1package tools.refinery.store.query.viatra.internal.matcher;
2 2
3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 3import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
4import tools.refinery.store.tuple.Tuple1; 4import tools.refinery.store.tuple.Tuple1;
5import tools.refinery.store.tuple.TupleLike; 5import tools.refinery.store.tuple.TupleLike;
6 6
7public record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike { 7record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike {
8 @Override 8 @Override
9 public int getSize() { 9 public int getSize() {
10 return wrappedTuple.getSize(); 10 return wrappedTuple.getSize();
@@ -15,4 +15,9 @@ public record ViatraTupleLike(ITuple wrappedTuple) implements TupleLike {
15 var wrappedValue = (Tuple1) wrappedTuple.get(element); 15 var wrappedValue = (Tuple1) wrappedTuple.get(element);
16 return wrappedValue.value0(); 16 return wrappedValue.value0();
17 } 17 }
18
19 @Override
20 public String toString() {
21 return TupleLike.toString(this);
22 }
18} 23}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
4import tools.refinery.store.query.term.Term;
5
6class AssumptionEvaluator extends TermEvaluator<Boolean> {
7 public AssumptionEvaluator(Term<Boolean> term) {
8 super(term);
9 }
10
11 @Override
12 public Object evaluateExpression(IValueProvider provider) {
13 var result = super.evaluateExpression(provider);
14 return result == null ? Boolean.FALSE : result;
15 }
16}
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
deleted file mode 100644
index 60f1bcae..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/DNF2PQuery.java
+++ /dev/null
@@ -1,223 +0,0 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
5import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
6import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
7import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
8import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Equality;
9import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.NegativePatternCall;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
17import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
19import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
20import tools.refinery.store.query.DNF;
21import tools.refinery.store.query.DNFAnd;
22import tools.refinery.store.query.DNFUtils;
23import tools.refinery.store.query.Variable;
24import tools.refinery.store.query.atom.*;
25import tools.refinery.store.query.view.AnyRelationView;
26
27import java.util.*;
28import java.util.function.Function;
29import java.util.stream.Collectors;
30
31public class DNF2PQuery {
32 private static final Object P_CONSTRAINT_LOCK = new Object();
33
34 private final Set<DNF> translating = new LinkedHashSet<>();
35
36 private final Map<DNF, RawPQuery> dnf2PQueryMap = new HashMap<>();
37
38 private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new LinkedHashMap<>();
39
40 private final Map<AnyRelationView, RawPQuery> view2EmbeddedMap = new HashMap<>();
41
42 private Function<DNF, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
43 QueryEvaluationHint.BackendRequirement.UNSPECIFIED);
44
45 public void setComputeHint(Function<DNF, QueryEvaluationHint> computeHint) {
46 this.computeHint = computeHint;
47 }
48
49 public RawPQuery translate(DNF dnfQuery) {
50 if (translating.contains(dnfQuery)) {
51 var path = translating.stream().map(DNF::name).collect(Collectors.joining(" -> "));
52 throw new IllegalStateException("Circular reference %s -> %s detected".formatted(path,
53 dnfQuery.name()));
54 }
55 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
56 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
57 var pQuery = dnf2PQueryMap.get(dnfQuery);
58 if (pQuery == null) {
59 translating.add(dnfQuery);
60 try {
61 pQuery = doTranslate(dnfQuery);
62 dnf2PQueryMap.put(dnfQuery, pQuery);
63 } finally {
64 translating.remove(dnfQuery);
65 }
66 }
67 return pQuery;
68 }
69
70 public Map<AnyRelationView, IInputKey> getRelationViews() {
71 return Collections.unmodifiableMap(view2WrapperMap);
72 }
73
74 public RawPQuery getAlreadyTranslated(DNF dnfQuery) {
75 return dnf2PQueryMap.get(dnfQuery);
76 }
77
78 private RawPQuery doTranslate(DNF dnfQuery) {
79 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
80 pQuery.setEvaluationHints(computeHint.apply(dnfQuery));
81
82 Map<Variable, PParameter> parameters = new HashMap<>();
83 for (Variable variable : dnfQuery.getParameters()) {
84 parameters.put(variable, new PParameter(variable.getUniqueName()));
85 }
86
87 List<PParameter> parameterList = new ArrayList<>();
88 for (var param : dnfQuery.getParameters()) {
89 parameterList.add(parameters.get(param));
90 }
91 pQuery.setParameters(parameterList);
92
93 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
94 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
95 for (var forEachVariable : functionalDependency.forEach()) {
96 functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName());
97 }
98 for (var uniqueVariable : functionalDependency.unique()) {
99 functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName());
100 }
101 pQuery.addAnnotation(functionalDependencyAnnotation);
102 }
103
104 // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates
105 // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating
106 // any query constraints to avoid a data race.
107 synchronized (P_CONSTRAINT_LOCK) {
108 for (DNFAnd clause : dnfQuery.getClauses()) {
109 PBody body = new PBody(pQuery);
110 List<ExportedParameter> symbolicParameters = new ArrayList<>();
111 for (var param : dnfQuery.getParameters()) {
112 PVariable pVar = body.getOrCreateVariableByName(param.getUniqueName());
113 symbolicParameters.add(new ExportedParameter(body, pVar, parameters.get(param)));
114 }
115 body.setSymbolicParameters(symbolicParameters);
116 pQuery.addBody(body);
117 for (DNFAtom constraint : clause.constraints()) {
118 translateDNFAtom(constraint, body);
119 }
120 }
121 }
122
123 return pQuery;
124 }
125
126 private void translateDNFAtom(DNFAtom constraint, PBody body) {
127 if (constraint instanceof EquivalenceAtom equivalenceAtom) {
128 translateEquivalenceAtom(equivalenceAtom, body);
129 } else if (constraint instanceof RelationViewAtom relationViewAtom) {
130 translateRelationViewAtom(relationViewAtom, body);
131 } else if (constraint instanceof DNFCallAtom callAtom) {
132 translateCallAtom(callAtom, body);
133 } else if (constraint instanceof ConstantAtom constantAtom) {
134 translateConstantAtom(constantAtom, body);
135 } else {
136 throw new IllegalArgumentException("Unknown constraint: " + constraint.toString());
137 }
138 }
139
140 private void translateEquivalenceAtom(EquivalenceAtom equivalence, PBody body) {
141 PVariable varSource = body.getOrCreateVariableByName(equivalence.left().getUniqueName());
142 PVariable varTarget = body.getOrCreateVariableByName(equivalence.right().getUniqueName());
143 if (equivalence.positive()) {
144 new Equality(body, varSource, varTarget);
145 } else {
146 new Inequality(body, varSource, varTarget);
147 }
148 }
149
150 private void translateRelationViewAtom(RelationViewAtom relationViewAtom, PBody body) {
151 var substitution = translateSubstitution(relationViewAtom.getSubstitution(), body);
152 var polarity = relationViewAtom.getPolarity();
153 var relationView = relationViewAtom.getTarget();
154 if (polarity == CallPolarity.POSITIVE) {
155 new TypeConstraint(body, substitution, wrapView(relationView));
156 } else {
157 var embeddedPQuery = translateEmbeddedRelationViewPQuery(relationView);
158 switch (polarity) {
159 case TRANSITIVE -> new BinaryTransitiveClosure(body, substitution, embeddedPQuery);
160 case NEGATIVE -> new NegativePatternCall(body, substitution, embeddedPQuery);
161 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
162 }
163 }
164 }
165
166 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
167 int arity = substitution.size();
168 Object[] variables = new Object[arity];
169 for (int i = 0; i < arity; i++) {
170 var variable = substitution.get(i);
171 variables[i] = body.getOrCreateVariableByName(variable.getUniqueName());
172 }
173 return Tuples.flatTupleOf(variables);
174 }
175
176 private RawPQuery translateEmbeddedRelationViewPQuery(AnyRelationView relationView) {
177 return view2EmbeddedMap.computeIfAbsent(relationView, this::doTranslateEmbeddedRelationViewPQuery);
178 }
179
180 private RawPQuery doTranslateEmbeddedRelationViewPQuery(AnyRelationView relationView) {
181 var embeddedPQuery = new RawPQuery(DNFUtils.generateUniqueName(relationView.name()), PVisibility.EMBEDDED);
182 var body = new PBody(embeddedPQuery);
183 int arity = relationView.arity();
184 var parameters = new ArrayList<PParameter>(arity);
185 var arguments = new Object[arity];
186 var symbolicParameters = new ArrayList<ExportedParameter>(arity);
187 for (int i = 0; i < arity; i++) {
188 var parameterName = "p" + i;
189 var parameter = new PParameter(parameterName);
190 parameters.add(parameter);
191 var variable = body.getOrCreateVariableByName(parameterName);
192 arguments[i] = variable;
193 symbolicParameters.add(new ExportedParameter(body, variable, parameter));
194 }
195 embeddedPQuery.setParameters(parameters);
196 body.setSymbolicParameters(symbolicParameters);
197 var argumentTuple = Tuples.flatTupleOf(arguments);
198 new TypeConstraint(body, argumentTuple, wrapView(relationView));
199 embeddedPQuery.addBody(body);
200 return embeddedPQuery;
201 }
202
203 private RelationViewWrapper wrapView(AnyRelationView relationView) {
204 return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new);
205 }
206
207 private void translateCallAtom(DNFCallAtom callAtom, PBody body) {
208 var variablesTuple = translateSubstitution(callAtom.getSubstitution(), body);
209 var translatedReferred = translate(callAtom.getTarget());
210 var polarity = callAtom.getPolarity();
211 switch (polarity) {
212 case POSITIVE -> new PositivePatternCall(body, variablesTuple, translatedReferred);
213 case TRANSITIVE -> new BinaryTransitiveClosure(body, variablesTuple, translatedReferred);
214 case NEGATIVE -> new NegativePatternCall(body, variablesTuple, translatedReferred);
215 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
216 }
217 }
218
219 private void translateConstantAtom(ConstantAtom constantAtom, PBody body) {
220 var variable = body.getOrCreateVariableByName(constantAtom.variable().getUniqueName());
221 new ConstantValue(body, variable, constantAtom.nodeId());
222 }
223}
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
new file mode 100644
index 00000000..7afeb977
--- /dev/null
+++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/Dnf2PQuery.java
@@ -0,0 +1,256 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory;
4import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
5import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
6import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
7import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
8import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator;
9import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
10import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation;
11import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*;
12import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure;
13import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
14import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
15import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
16import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
17import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
18import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
19import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
20import tools.refinery.store.query.dnf.Dnf;
21import tools.refinery.store.query.dnf.DnfClause;
22import tools.refinery.store.query.literal.*;
23import tools.refinery.store.query.term.ConstantTerm;
24import tools.refinery.store.query.term.StatefulAggregator;
25import tools.refinery.store.query.term.StatelessAggregator;
26import tools.refinery.store.query.term.Variable;
27import tools.refinery.store.query.view.AnyRelationView;
28import tools.refinery.store.util.CycleDetectingMapper;
29
30import java.util.*;
31import java.util.function.Function;
32import java.util.stream.Collectors;
33
34public class Dnf2PQuery {
35 private static final Object P_CONSTRAINT_LOCK = new Object();
36 private final CycleDetectingMapper<Dnf, RawPQuery> mapper = new CycleDetectingMapper<>(Dnf::name,
37 this::doTranslate);
38 private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this);
39 private final Map<Dnf, QueryEvaluationHint> hintOverrides = new LinkedHashMap<>();
40 private Function<Dnf, QueryEvaluationHint> computeHint = dnf -> new QueryEvaluationHint(null,
41 (IQueryBackendFactory) null);
42
43 public void setComputeHint(Function<Dnf, QueryEvaluationHint> computeHint) {
44 this.computeHint = computeHint;
45 }
46
47 public RawPQuery translate(Dnf dnfQuery) {
48 return mapper.map(dnfQuery);
49 }
50
51 public Map<AnyRelationView, IInputKey> getRelationViews() {
52 return wrapperFactory.getRelationViews();
53 }
54
55 public void hint(Dnf dnf, QueryEvaluationHint hint) {
56 hintOverrides.compute(dnf, (ignoredKey, existingHint) ->
57 existingHint == null ? hint : existingHint.overrideBy(hint));
58 }
59
60 private QueryEvaluationHint consumeHint(Dnf dnf) {
61 var defaultHint = computeHint.apply(dnf);
62 var existingHint = hintOverrides.remove(dnf);
63 return defaultHint.overrideBy(existingHint);
64 }
65
66 public void assertNoUnusedHints() {
67 if (hintOverrides.isEmpty()) {
68 return;
69 }
70 var unusedHints = hintOverrides.keySet().stream().map(Dnf::name).collect(Collectors.joining(", "));
71 throw new IllegalStateException(
72 "Unused query evaluation hints for %s. Hints must be set before a query is added to the engine"
73 .formatted(unusedHints));
74 }
75
76 private RawPQuery doTranslate(Dnf dnfQuery) {
77 var pQuery = new RawPQuery(dnfQuery.getUniqueName());
78 pQuery.setEvaluationHints(consumeHint(dnfQuery));
79
80 Map<Variable, PParameter> parameters = new HashMap<>();
81 for (Variable variable : dnfQuery.getParameters()) {
82 parameters.put(variable, new PParameter(variable.getUniqueName()));
83 }
84
85 List<PParameter> parameterList = new ArrayList<>();
86 for (var param : dnfQuery.getParameters()) {
87 parameterList.add(parameters.get(param));
88 }
89 pQuery.setParameters(parameterList);
90
91 for (var functionalDependency : dnfQuery.getFunctionalDependencies()) {
92 var functionalDependencyAnnotation = new PAnnotation("FunctionalDependency");
93 for (var forEachVariable : functionalDependency.forEach()) {
94 functionalDependencyAnnotation.addAttribute("forEach", forEachVariable.getUniqueName());
95 }
96 for (var uniqueVariable : functionalDependency.unique()) {
97 functionalDependencyAnnotation.addAttribute("unique", uniqueVariable.getUniqueName());
98 }
99 pQuery.addAnnotation(functionalDependencyAnnotation);
100 }
101
102 // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates
103 // global static state (<code>nextID</code>) without locking. Therefore, we need to synchronize before creating
104 // any query literals to avoid a data race.
105 synchronized (P_CONSTRAINT_LOCK) {
106 for (DnfClause clause : dnfQuery.getClauses()) {
107 PBody body = new PBody(pQuery);
108 List<ExportedParameter> symbolicParameters = new ArrayList<>();
109 for (var param : dnfQuery.getParameters()) {
110 PVariable pVar = body.getOrCreateVariableByName(param.getUniqueName());
111 symbolicParameters.add(new ExportedParameter(body, pVar, parameters.get(param)));
112 }
113 body.setSymbolicParameters(symbolicParameters);
114 pQuery.addBody(body);
115 for (Literal literal : clause.literals()) {
116 translateLiteral(literal, clause, body);
117 }
118 }
119 }
120
121 return pQuery;
122 }
123
124 private void translateLiteral(Literal literal, DnfClause clause, PBody body) {
125 if (literal instanceof EquivalenceLiteral equivalenceLiteral) {
126 translateEquivalenceLiteral(equivalenceLiteral, body);
127 } else if (literal instanceof CallLiteral callLiteral) {
128 translateCallLiteral(callLiteral, clause, body);
129 } else if (literal instanceof ConstantLiteral constantLiteral) {
130 translateConstantLiteral(constantLiteral, body);
131 } else if (literal instanceof AssignLiteral<?> assignLiteral) {
132 translateAssignLiteral(assignLiteral, body);
133 } else if (literal instanceof AssumeLiteral assumeLiteral) {
134 translateAssumeLiteral(assumeLiteral, body);
135 } else if (literal instanceof CountLiteral countLiteral) {
136 translateCountLiteral(countLiteral, clause, body);
137 } else if (literal instanceof AggregationLiteral<?, ?> aggregationLiteral) {
138 translateAggregationLiteral(aggregationLiteral, clause, body);
139 } else {
140 throw new IllegalArgumentException("Unknown literal: " + literal.toString());
141 }
142 }
143
144 private void translateEquivalenceLiteral(EquivalenceLiteral equivalenceLiteral, PBody body) {
145 PVariable varSource = body.getOrCreateVariableByName(equivalenceLiteral.left().getUniqueName());
146 PVariable varTarget = body.getOrCreateVariableByName(equivalenceLiteral.right().getUniqueName());
147 if (equivalenceLiteral.positive()) {
148 new Equality(body, varSource, varTarget);
149 } else {
150 new Inequality(body, varSource, varTarget);
151 }
152 }
153
154 private void translateCallLiteral(CallLiteral callLiteral, DnfClause clause, PBody body) {
155 var polarity = callLiteral.getPolarity();
156 switch (polarity) {
157 case POSITIVE -> {
158 var substitution = translateSubstitution(callLiteral.getArguments(), body);
159 var constraint = callLiteral.getTarget();
160 if (constraint instanceof Dnf dnf) {
161 var pattern = translate(dnf);
162 new PositivePatternCall(body, substitution, pattern);
163 } else if (constraint instanceof AnyRelationView relationView) {
164 var inputKey = wrapperFactory.getInputKey(relationView);
165 new TypeConstraint(body, substitution, inputKey);
166 } else {
167 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
168 }
169 }
170 case TRANSITIVE -> {
171 var substitution = translateSubstitution(callLiteral.getArguments(), body);
172 var constraint = callLiteral.getTarget();
173 PQuery pattern;
174 if (constraint instanceof Dnf dnf) {
175 pattern = translate(dnf);
176 } else if (constraint instanceof AnyRelationView relationView) {
177 pattern = wrapperFactory.wrapRelationViewIdentityArguments(relationView);
178 } else {
179 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
180 }
181 new BinaryTransitiveClosure(body, substitution, pattern);
182 }
183 case NEGATIVE -> {
184 var wrappedCall = wrapperFactory.maybeWrapConstraint(callLiteral, clause);
185 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
186 var pattern = wrappedCall.pattern();
187 new NegativePatternCall(body, substitution, pattern);
188 }
189 default -> throw new IllegalArgumentException("Unknown polarity: " + polarity);
190 }
191 }
192
193 private static Tuple translateSubstitution(List<Variable> substitution, PBody body) {
194 int arity = substitution.size();
195 Object[] variables = new Object[arity];
196 for (int i = 0; i < arity; i++) {
197 var variable = substitution.get(i);
198 variables[i] = body.getOrCreateVariableByName(variable.getUniqueName());
199 }
200 return Tuples.flatTupleOf(variables);
201 }
202
203 private void translateConstantLiteral(ConstantLiteral constantLiteral, PBody body) {
204 var variable = body.getOrCreateVariableByName(constantLiteral.variable().getUniqueName());
205 new ConstantValue(body, variable, constantLiteral.nodeId());
206 }
207
208 private <T> void translateAssignLiteral(AssignLiteral<T> assignLiteral, PBody body) {
209 var variable = body.getOrCreateVariableByName(assignLiteral.variable().getUniqueName());
210 var term = assignLiteral.term();
211 if (term instanceof ConstantTerm<T> constantTerm) {
212 new ConstantValue(body, variable, constantTerm.getValue());
213 } else {
214 var evaluator = new TermEvaluator<>(term);
215 new ExpressionEvaluation(body, evaluator, variable);
216 }
217 }
218
219 private void translateAssumeLiteral(AssumeLiteral assumeLiteral, PBody body) {
220 var evaluator = new AssumptionEvaluator(assumeLiteral.term());
221 new ExpressionEvaluation(body, evaluator, null);
222 }
223
224 private void translateCountLiteral(CountLiteral countLiteral, DnfClause clause, PBody body) {
225 var wrappedCall = wrapperFactory.maybeWrapConstraint(countLiteral, clause);
226 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
227 var resultVariable = body.getOrCreateVariableByName(countLiteral.getResultVariable().getUniqueName());
228 new PatternMatchCounter(body, substitution, wrappedCall.pattern(), resultVariable);
229 }
230
231 private <R, T> void translateAggregationLiteral(AggregationLiteral<R, T> aggregationLiteral, DnfClause clause,
232 PBody body) {
233 var aggregator = aggregationLiteral.getAggregator();
234 IMultisetAggregationOperator<T, ?, R> aggregationOperator;
235 if (aggregator instanceof StatelessAggregator<R, T> statelessAggregator) {
236 aggregationOperator = new StatelessMultisetAggregator<>(statelessAggregator);
237 } else if (aggregator instanceof StatefulAggregator<R, T> statefulAggregator) {
238 aggregationOperator = new StatefulMultisetAggregator<>(statefulAggregator);
239 } else {
240 throw new IllegalArgumentException("Unknown aggregator: " + aggregator);
241 }
242 var wrappedCall = wrapperFactory.maybeWrapConstraint(aggregationLiteral, clause);
243 var substitution = translateSubstitution(wrappedCall.remappedArguments(), body);
244 var inputVariable = body.getOrCreateVariableByName(aggregationLiteral.getInputVariable().getUniqueName());
245 var aggregatedColumn = substitution.invertIndex().get(inputVariable);
246 if (aggregatedColumn == null) {
247 throw new IllegalStateException("Input variable %s not found in substitution %s".formatted(inputVariable,
248 substitution));
249 }
250 var boundAggregator = new BoundAggregator(aggregationOperator, aggregator.getInputType(),
251 aggregator.getResultType());
252 var resultVariable = body.getOrCreateVariableByName(aggregationLiteral.getResultVariable().getUniqueName());
253 new AggregatorConstraint(boundAggregator, body, substitution, wrappedCall.pattern(), resultVariable,
254 aggregatedColumn);
255 }
256}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
4import org.eclipse.viatra.query.runtime.matchers.psystem.PBody;
5import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
6import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
7import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
8import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
9import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery;
11import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
12import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
13import tools.refinery.store.query.Constraint;
14import tools.refinery.store.query.dnf.Dnf;
15import tools.refinery.store.query.dnf.DnfClause;
16import tools.refinery.store.query.dnf.DnfUtils;
17import tools.refinery.store.query.literal.AbstractCallLiteral;
18import tools.refinery.store.query.term.Variable;
19import tools.refinery.store.query.view.AnyRelationView;
20import tools.refinery.store.query.view.RelationView;
21import tools.refinery.store.util.CycleDetectingMapper;
22
23import java.util.*;
24import java.util.function.ToIntFunction;
25
26class QueryWrapperFactory {
27 private final Dnf2PQuery dnf2PQuery;
28 private final Map<AnyRelationView, RelationViewWrapper> view2WrapperMap = new LinkedHashMap<>();
29 private final CycleDetectingMapper<RemappedConstraint, RawPQuery> wrapConstraint = new CycleDetectingMapper<>(
30 RemappedConstraint::toString, this::doWrapConstraint);
31
32 QueryWrapperFactory(Dnf2PQuery dnf2PQuery) {
33 this.dnf2PQuery = dnf2PQuery;
34 }
35
36 public PQuery wrapRelationViewIdentityArguments(AnyRelationView relationView) {
37 var identity = new int[relationView.arity()];
38 for (int i = 0; i < identity.length; i++) {
39 identity[i] = i;
40 }
41 return maybeWrapConstraint(relationView, identity);
42 }
43 public WrappedCall maybeWrapConstraint(AbstractCallLiteral callLiteral, DnfClause clause) {
44 var arguments = callLiteral.getArguments();
45 int arity = arguments.size();
46 var remappedParameters = new int[arity];
47 var boundVariables = clause.boundVariables();
48 var unboundVariableIndices = new HashMap<Variable, Integer>();
49 var appendVariable = new VariableAppender();
50 for (int i = 0; i < arity; i++) {
51 var variable = arguments.get(i);
52 if (boundVariables.contains(variable)) {
53 // Do not join bound variable to make sure that the embedded pattern stays as general as possible.
54 remappedParameters[i] = appendVariable.applyAsInt(variable);
55 } else {
56 remappedParameters[i] = unboundVariableIndices.computeIfAbsent(variable, appendVariable::applyAsInt);
57 }
58 }
59 var pattern = maybeWrapConstraint(callLiteral.getTarget(), remappedParameters);
60 return new WrappedCall(pattern, appendVariable.getRemappedArguments());
61 }
62
63 private PQuery maybeWrapConstraint(Constraint constraint, int[] remappedParameters) {
64 if (remappedParameters.length != constraint.arity()) {
65 throw new IllegalArgumentException("Constraint %s expected %d parameters, but got %d parameters".formatted(
66 constraint, constraint.arity(), remappedParameters.length));
67 }
68 if (constraint instanceof Dnf dnf && isIdentity(remappedParameters)) {
69 return dnf2PQuery.translate(dnf);
70 }
71 return wrapConstraint.map(new RemappedConstraint(constraint, remappedParameters));
72 }
73
74 private static boolean isIdentity(int[] remappedParameters) {
75 for (int i = 0; i < remappedParameters.length; i++) {
76 if (remappedParameters[i] != i) {
77 return false;
78 }
79 }
80 return true;
81 }
82
83 private RawPQuery doWrapConstraint(RemappedConstraint remappedConstraint) {
84 var constraint = remappedConstraint.constraint();
85 var remappedParameters = remappedConstraint.remappedParameters();
86
87 var embeddedPQuery = new RawPQuery(DnfUtils.generateUniqueName(constraint.name()), PVisibility.EMBEDDED);
88 var body = new PBody(embeddedPQuery);
89 int arity = Arrays.stream(remappedParameters).max().orElse(-1) + 1;
90 var parameters = new ArrayList<PParameter>(arity);
91 var parameterVariables = new PVariable[arity];
92 var symbolicParameters = new ArrayList<ExportedParameter>(arity);
93 for (int i = 0; i < arity; i++) {
94 var parameterName = "p" + i;
95 var parameter = new PParameter(parameterName);
96 parameters.add(parameter);
97 var variable = body.getOrCreateVariableByName(parameterName);
98 parameterVariables[i] = variable;
99 symbolicParameters.add(new ExportedParameter(body, variable, parameter));
100 }
101 embeddedPQuery.setParameters(parameters);
102 body.setSymbolicParameters(symbolicParameters);
103
104 var arguments = new Object[remappedParameters.length];
105 for (int i = 0; i < remappedParameters.length; i++) {
106 arguments[i] = parameterVariables[remappedParameters[i]];
107 }
108 var argumentTuple = Tuples.flatTupleOf(arguments);
109
110 if (constraint instanceof RelationView<?> relationView) {
111 new TypeConstraint(body, argumentTuple, getInputKey(relationView));
112 } else if (constraint instanceof Dnf dnf) {
113 var calledPQuery = dnf2PQuery.translate(dnf);
114 new PositivePatternCall(body, argumentTuple, calledPQuery);
115 } else {
116 throw new IllegalArgumentException("Unknown Constraint: " + constraint);
117 }
118
119 embeddedPQuery.addBody(body);
120 return embeddedPQuery;
121 }
122
123 public IInputKey getInputKey(AnyRelationView relationView) {
124 return view2WrapperMap.computeIfAbsent(relationView, RelationViewWrapper::new);
125 }
126
127 public Map<AnyRelationView, IInputKey> getRelationViews() {
128 return Collections.unmodifiableMap(view2WrapperMap);
129 }
130
131 public record WrappedCall(PQuery pattern, List<Variable> remappedArguments) {
132 }
133
134 private static class VariableAppender implements ToIntFunction<Variable> {
135 private final List<Variable> remappedArguments = new ArrayList<>();
136 private int nextIndex = 0;
137
138 @Override
139 public int applyAsInt(Variable variable) {
140 remappedArguments.add(variable);
141 int index = nextIndex;
142 nextIndex++;
143 return index;
144 }
145
146 public List<Variable> getRemappedArguments() {
147 return remappedArguments;
148 }
149 }
150
151 private record RemappedConstraint(Constraint constraint, int[] remappedParameters) {
152 @Override
153 public boolean equals(Object o) {
154 if (this == o) return true;
155 if (o == null || getClass() != o.getClass()) return false;
156 RemappedConstraint that = (RemappedConstraint) o;
157 return constraint.equals(that.constraint) && Arrays.equals(remappedParameters, that.remappedParameters);
158 }
159
160 @Override
161 public int hashCode() {
162 int result = Objects.hash(constraint);
163 result = 31 * result + Arrays.hashCode(remappedParameters);
164 return result;
165 }
166
167 @Override
168 public String toString() {
169 return "RemappedConstraint{constraint=%s, remappedParameters=%s}".formatted(constraint,
170 Arrays.toString(remappedParameters));
171 }
172 }
173}
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;
9import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; 9import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; 10import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility;
11import tools.refinery.store.query.viatra.internal.RelationalScope; 11import tools.refinery.store.query.viatra.internal.RelationalScope;
12import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher;
12 13
13import java.util.LinkedHashSet; 14import java.util.LinkedHashSet;
14import java.util.List; 15import 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 e944e873..00000000
--- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RawPatternMatcher.java
+++ /dev/null
@@ -1,72 +0,0 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher;
4import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification;
5import tools.refinery.store.query.ResultSet;
6import tools.refinery.store.query.viatra.ViatraTupleLike;
7import tools.refinery.store.tuple.Tuple;
8import tools.refinery.store.tuple.TupleLike;
9
10import java.util.Optional;
11import java.util.stream.Stream;
12
13public class RawPatternMatcher extends GenericPatternMatcher implements ResultSet {
14 protected final Object[] empty;
15
16 public RawPatternMatcher(GenericQuerySpecification<? extends GenericPatternMatcher> specification) {
17 super(specification);
18 empty = new Object[specification.getParameterNames().size()];
19 }
20
21 @Override
22 public boolean hasResult() {
23 return backend.hasMatch(empty);
24 }
25
26 @Override
27 public boolean hasResult(Tuple parameters) {
28 return backend.hasMatch(toParametersArray(parameters));
29 }
30
31 @Override
32 public Optional<TupleLike> oneResult() {
33 return backend.getOneArbitraryMatch(empty).map(ViatraTupleLike::new);
34 }
35
36 @Override
37 public Optional<TupleLike> oneResult(Tuple parameters) {
38 return backend.getOneArbitraryMatch(toParametersArray(parameters)).map(ViatraTupleLike::new);
39 }
40
41 @Override
42 public Stream<TupleLike> allResults() {
43 return backend.getAllMatches(empty).map(ViatraTupleLike::new);
44 }
45
46 @Override
47 public Stream<TupleLike> allResults(Tuple parameters) {
48 return backend.getAllMatches(toParametersArray(parameters)).map(ViatraTupleLike::new);
49 }
50
51 @Override
52 public int countResults() {
53 return backend.countMatches(empty);
54 }
55
56 @Override
57 public int countResults(Tuple parameters) {
58 return backend.countMatches(toParametersArray(parameters));
59 }
60
61 private Object[] toParametersArray(Tuple tuple) {
62 int size = tuple.getSize();
63 var array = new Object[tuple.getSize()];
64 for (int i = 0; i < size; i++) {
65 var value = tuple.get(i);
66 if (value >= 0) {
67 array[i] = Tuple.of(value);
68 }
69 }
70 return array;
71 }
72}
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<AnyRelationView> {
27 public boolean isEnumerable() { 27 public boolean isEnumerable() {
28 return true; 28 return true;
29 } 29 }
30
31 @Override
32 public String toString() {
33 return "RelationViewWrapper{wrappedKey=%s}".formatted(wrappedKey);
34 }
30} 35}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
4import tools.refinery.store.query.term.StatefulAggregate;
5import tools.refinery.store.query.term.StatefulAggregator;
6
7import java.util.stream.Stream;
8
9record StatefulMultisetAggregator<R, T>(StatefulAggregator<R, T> aggregator)
10 implements IMultisetAggregationOperator<T, StatefulAggregate<R, T>, R> {
11 @Override
12 public String getShortDescription() {
13 return getName();
14 }
15
16 @Override
17 public String getName() {
18 return aggregator.toString();
19 }
20
21 @Override
22 public StatefulAggregate<R, T> createNeutral() {
23 return aggregator.createEmptyAggregate();
24 }
25
26 @Override
27 public boolean isNeutral(StatefulAggregate<R, T> result) {
28 return result.isEmpty();
29 }
30
31 @Override
32 public StatefulAggregate<R, T> update(StatefulAggregate<R, T> oldResult, T updateValue, boolean isInsertion) {
33 if (isInsertion) {
34 oldResult.add(updateValue);
35 } else {
36 oldResult.remove(updateValue);
37 }
38 return oldResult;
39 }
40
41 @Override
42 public R getAggregate(StatefulAggregate<R, T> result) {
43 return result.getResult();
44 }
45
46 @Override
47 public R aggregateStream(Stream<T> stream) {
48 return aggregator.aggregateStream(stream);
49 }
50
51 @Override
52 public StatefulAggregate<R, T> clone(StatefulAggregate<R, T> original) {
53 return original.deepCopy();
54 }
55
56 @Override
57 public boolean contains(T value, StatefulAggregate<R, T> accumulator) {
58 return accumulator.contains(value);
59 }
60}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator;
4import tools.refinery.store.query.term.StatelessAggregator;
5
6import java.util.stream.Stream;
7
8record StatelessMultisetAggregator<R, T>(StatelessAggregator<R, T> aggregator)
9 implements IMultisetAggregationOperator<T, R, R> {
10 @Override
11 public String getShortDescription() {
12 return getName();
13 }
14
15 @Override
16 public String getName() {
17 return aggregator.toString();
18 }
19
20 @Override
21 public R createNeutral() {
22 return aggregator.getEmptyResult();
23 }
24
25 @Override
26 public boolean isNeutral(R result) {
27 return createNeutral().equals(result);
28 }
29
30 @Override
31 public R update(R oldResult, T updateValue, boolean isInsertion) {
32 return isInsertion ? aggregator.add(oldResult, updateValue) : aggregator.remove(oldResult, updateValue);
33 }
34
35 @Override
36 public R getAggregate(R result) {
37 return result;
38 }
39
40 @Override
41 public R clone(R original) {
42 // Aggregate result is immutable.
43 return original;
44 }
45
46 @Override
47 public R aggregateStream(Stream<T> stream) {
48 return aggregator.aggregateStream(stream);
49 }
50}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator;
4import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
5import tools.refinery.store.query.term.Term;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.stream.Collectors;
9
10class TermEvaluator<T> implements IExpressionEvaluator {
11 private final Term<T> term;
12
13 public TermEvaluator(Term<T> term) {
14 this.term = term;
15 }
16
17 @Override
18 public String getShortDescription() {
19 return term.toString();
20 }
21
22 @Override
23 public Iterable<String> getInputParameterNames() {
24 return term.getInputVariables().stream().map(Variable::getUniqueName).collect(Collectors.toUnmodifiableSet());
25 }
26
27 @Override
28 public Object evaluateExpression(IValueProvider provider) {
29 var valuation = new ValueProviderBasedValuation(provider);
30 return term.evaluate(valuation);
31 }
32}
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 @@
1package tools.refinery.store.query.viatra.internal.pquery;
2
3import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.valuation.Valuation;
6
7public record ValueProviderBasedValuation(IValueProvider valueProvider) implements Valuation {
8 @Override
9 public <T> T getValue(DataVariable<T> variable) {
10 @SuppressWarnings("unchecked")
11 var value = (T) valueProvider.getValue(variable.getUniqueName());
12 return value;
13 }
14}
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 {
22 } 22 }
23 23
24 private <T> void registerView(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) { 24 private <T> void registerView(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) {
25 var listener = RelationViewUpdateListener.of(adapter, relationView);
26 var model = adapter.getModel(); 25 var model = adapter.getModel();
27 var interpretation = model.getInterpretation(relationView.getSymbol()); 26 var interpretation = model.getInterpretation(relationView.getSymbol());
28 interpretation.addListener(listener, true); 27 var listener = RelationViewUpdateListener.of(adapter, relationView, interpretation);
29 relationViewUpdateListeners.put(relationView, listener); 28 relationViewUpdateListeners.put(relationView, listener);
30 } 29 }
31 30
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;
4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; 4import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener;
5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; 5import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple;
6import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; 6import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;
7import tools.refinery.store.model.Interpretation;
7import tools.refinery.store.model.InterpretationListener; 8import tools.refinery.store.model.InterpretationListener;
8import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 9import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
9import tools.refinery.store.query.view.RelationView; 10import tools.refinery.store.query.view.RelationView;
@@ -14,18 +15,27 @@ import java.util.List;
14 15
15public abstract class RelationViewUpdateListener<T> implements InterpretationListener<T> { 16public abstract class RelationViewUpdateListener<T> implements InterpretationListener<T> {
16 private final ViatraModelQueryAdapterImpl adapter; 17 private final ViatraModelQueryAdapterImpl adapter;
18 private final Interpretation<T> interpretation;
17 private final List<RelationViewFilter> filters = new ArrayList<>(); 19 private final List<RelationViewFilter> filters = new ArrayList<>();
18 20
19 protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter) { 21 protected RelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, Interpretation<T> interpretation) {
20 this.adapter = adapter; 22 this.adapter = adapter;
23 this.interpretation = interpretation;
21 } 24 }
22 25
23 public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { 26 public void addFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
27 if (filters.isEmpty()) {
28 // First filter to be added, from now on we have to subscribe to model updates.
29 interpretation.addListener(this, true);
30 }
24 filters.add(new RelationViewFilter(inputKey, seed, listener)); 31 filters.add(new RelationViewFilter(inputKey, seed, listener));
25 } 32 }
26 33
27 public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) { 34 public void removeFilter(IInputKey inputKey, ITuple seed, IQueryRuntimeContextListener listener) {
28 filters.remove(new RelationViewFilter(inputKey, seed, listener)); 35 if (filters.remove(new RelationViewFilter(inputKey, seed, listener)) && filters.isEmpty()) {
36 // Last listener to be added, we don't have be subscribed to model updates anymore.
37 interpretation.removeListener(this);
38 }
29 } 39 }
30 40
31 protected void processUpdate(Tuple tuple, boolean isInsertion) { 41 protected void processUpdate(Tuple tuple, boolean isInsertion) {
@@ -39,10 +49,12 @@ public abstract class RelationViewUpdateListener<T> implements InterpretationLis
39 } 49 }
40 50
41 public static <T> RelationViewUpdateListener<T> of(ViatraModelQueryAdapterImpl adapter, 51 public static <T> RelationViewUpdateListener<T> of(ViatraModelQueryAdapterImpl adapter,
42 RelationView<T> relationView) { 52 RelationView<T> relationView,
53 Interpretation<T> interpretation) {
43 if (relationView instanceof TuplePreservingRelationView<T> tuplePreservingRelationView) { 54 if (relationView instanceof TuplePreservingRelationView<T> tuplePreservingRelationView) {
44 return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView); 55 return new TuplePreservingRelationViewUpdateListener<>(adapter, tuplePreservingRelationView,
56 interpretation);
45 } 57 }
46 return new TupleChangingRelationViewUpdateListener<>(adapter, relationView); 58 return new TupleChangingRelationViewUpdateListener<>(adapter, relationView, interpretation);
47 } 59 }
48} 60}
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 @@
1package tools.refinery.store.query.viatra.internal.update; 1package tools.refinery.store.query.viatra.internal.update;
2 2
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
4import tools.refinery.store.model.Interpretation;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 5import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.RelationView; 6import tools.refinery.store.query.view.RelationView;
6import tools.refinery.store.tuple.Tuple; 7import tools.refinery.store.tuple.Tuple;
@@ -10,8 +11,9 @@ import java.util.Arrays;
10public class TupleChangingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> { 11public class TupleChangingRelationViewUpdateListener<T> extends RelationViewUpdateListener<T> {
11 private final RelationView<T> relationView; 12 private final RelationView<T> relationView;
12 13
13 TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView) { 14 TupleChangingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, RelationView<T> relationView,
14 super(adapter); 15 Interpretation<T> interpretation) {
16 super(adapter, interpretation);
15 this.relationView = relationView; 17 this.relationView = relationView;
16 } 18 }
17 19
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 @@
1package tools.refinery.store.query.viatra.internal.update; 1package tools.refinery.store.query.viatra.internal.update;
2 2
3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; 3import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples;
4import tools.refinery.store.model.Interpretation;
4import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 5import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl;
5import tools.refinery.store.query.view.TuplePreservingRelationView; 6import tools.refinery.store.query.view.TuplePreservingRelationView;
6import tools.refinery.store.tuple.Tuple; 7import tools.refinery.store.tuple.Tuple;
@@ -9,8 +10,8 @@ public class TuplePreservingRelationViewUpdateListener<T> extends RelationViewUp
9 private final TuplePreservingRelationView<T> view; 10 private final TuplePreservingRelationView<T> view;
10 11
11 TuplePreservingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter, 12 TuplePreservingRelationViewUpdateListener(ViatraModelQueryAdapterImpl adapter,
12 TuplePreservingRelationView<T> view) { 13 TuplePreservingRelationView<T> view, Interpretation<T> interpretation) {
13 super(adapter); 14 super(adapter, interpretation);
14 this.view = view; 15 this.view = view;
15 } 16 }
16 17
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 @@
1package tools.refinery.store.query.viatra;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.ModelQuery;
6import tools.refinery.store.query.dnf.Dnf;
7import tools.refinery.store.query.dnf.Query;
8import tools.refinery.store.query.term.Variable;
9import tools.refinery.store.query.viatra.tests.QueryEngineTest;
10import tools.refinery.store.query.view.FunctionalRelationView;
11import tools.refinery.store.query.view.KeyOnlyRelationView;
12import tools.refinery.store.representation.Symbol;
13import tools.refinery.store.tuple.Tuple;
14
15import java.util.Map;
16import java.util.Optional;
17
18import static tools.refinery.store.query.literal.Literals.not;
19import static tools.refinery.store.query.term.int_.IntTerms.INT_SUM;
20import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
21import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
22
23class DiagonalQueryTest {
24 @QueryEngineTest
25 void inputKeyNegationTest(QueryEvaluationHint hint) {
26 var person = new Symbol<>("Person", 1, Boolean.class, false);
27 var symbol = new Symbol<>("symbol", 4, Boolean.class, false);
28 var personView = new KeyOnlyRelationView<>(person);
29 var symbolView = new KeyOnlyRelationView<>(symbol);
30
31 var p1 = Variable.of("p1");
32 var p2 = Variable.of("p2");
33 var query = Query.builder("Diagonal")
34 .parameter(p1)
35 .clause(
36 personView.call(p1),
37 not(symbolView.call(p1, p1, p2, p2))
38 )
39 .build();
40
41 var store = ModelStore.builder()
42 .symbols(person, symbol)
43 .with(ViatraModelQuery.ADAPTER)
44 .defaultHint(hint)
45 .queries(query)
46 .build();
47
48 var model = store.createEmptyModel();
49 var personInterpretation = model.getInterpretation(person);
50 var symbolInterpretation = model.getInterpretation(symbol);
51 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
52 var queryResultSet = queryEngine.getResultSet(query);
53
54 personInterpretation.put(Tuple.of(0), true);
55 personInterpretation.put(Tuple.of(1), true);
56 personInterpretation.put(Tuple.of(2), true);
57
58 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
59 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
60 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
61 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
62
63 queryEngine.flushChanges();
64 assertResults(Map.of(
65 Tuple.of(0), false,
66 Tuple.of(1), true,
67 Tuple.of(2), true,
68 Tuple.of(3), false
69 ), queryResultSet);
70 }
71
72 @QueryEngineTest
73 void subQueryNegationTest(QueryEvaluationHint hint) {
74 var person = new Symbol<>("Person", 1, Boolean.class, false);
75 var symbol = new Symbol<>("symbol", 4, Boolean.class, false);
76 var personView = new KeyOnlyRelationView<>(person);
77 var symbolView = new KeyOnlyRelationView<>(symbol);
78
79 var p1 = Variable.of("p1");
80 var p2 = Variable.of("p2");
81 var p3 = Variable.of("p3");
82 var p4 = Variable.of("p4");
83 var subQuery = Dnf.builder("SubQuery")
84 .parameters(p1, p2, p3, p4)
85 .clause(
86 personView.call(p1),
87 symbolView.call(p1, p2, p3, p4)
88 )
89 .clause(
90 personView.call(p2),
91 symbolView.call(p1, p2, p3, p4)
92 )
93 .build();
94 var query = Query.builder("Diagonal")
95 .parameter(p1)
96 .clause(
97 personView.call(p1),
98 not(subQuery.call(p1, p1, p2, p2))
99 )
100 .build();
101
102 var store = ModelStore.builder()
103 .symbols(person, symbol)
104 .with(ViatraModelQuery.ADAPTER)
105 .defaultHint(hint)
106 .queries(query)
107 .build();
108
109 var model = store.createEmptyModel();
110 var personInterpretation = model.getInterpretation(person);
111 var symbolInterpretation = model.getInterpretation(symbol);
112 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
113 var queryResultSet = queryEngine.getResultSet(query);
114
115 personInterpretation.put(Tuple.of(0), true);
116 personInterpretation.put(Tuple.of(1), true);
117 personInterpretation.put(Tuple.of(2), true);
118
119 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
120 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
121 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
122 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
123
124 queryEngine.flushChanges();
125 assertResults(Map.of(
126 Tuple.of(0), false,
127 Tuple.of(1), true,
128 Tuple.of(2), true,
129 Tuple.of(3), false
130 ), queryResultSet);
131 }
132
133 @QueryEngineTest
134 void inputKeyCountTest(QueryEvaluationHint hint) {
135 var person = new Symbol<>("Person", 1, Boolean.class, false);
136 var symbol = new Symbol<>("symbol", 4, Boolean.class, false);
137 var personView = new KeyOnlyRelationView<>(person);
138 var symbolView = new KeyOnlyRelationView<>(symbol);
139
140 var p1 = Variable.of("p1");
141 var p2 = Variable.of("p2");
142 var x = Variable.of("x", Integer.class);
143 var query = Query.builder("Diagonal")
144 .parameter(p1)
145 .output(x)
146 .clause(
147 personView.call(p1),
148 x.assign(symbolView.count(p1, p1, p2, p2))
149 )
150 .build();
151
152 var store = ModelStore.builder()
153 .symbols(person, symbol)
154 .with(ViatraModelQuery.ADAPTER)
155 .defaultHint(hint)
156 .queries(query)
157 .build();
158
159 var model = store.createEmptyModel();
160 var personInterpretation = model.getInterpretation(person);
161 var symbolInterpretation = model.getInterpretation(symbol);
162 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
163 var queryResultSet = queryEngine.getResultSet(query);
164
165 personInterpretation.put(Tuple.of(0), true);
166 personInterpretation.put(Tuple.of(1), true);
167 personInterpretation.put(Tuple.of(2), true);
168
169 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
170 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true);
171 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
172 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
173 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
174
175 queryEngine.flushChanges();
176 assertNullableResults(Map.of(
177 Tuple.of(0), Optional.of(2),
178 Tuple.of(1), Optional.of(0),
179 Tuple.of(2), Optional.of(0),
180 Tuple.of(3), Optional.empty()
181 ), queryResultSet);
182 }
183
184 @QueryEngineTest
185 void subQueryCountTest(QueryEvaluationHint hint) {
186 var person = new Symbol<>("Person", 1, Boolean.class, false);
187 var symbol = new Symbol<>("symbol", 4, Boolean.class, false);
188 var personView = new KeyOnlyRelationView<>(person);
189 var symbolView = new KeyOnlyRelationView<>(symbol);
190
191 var p1 = Variable.of("p1");
192 var p2 = Variable.of("p2");
193 var p3 = Variable.of("p3");
194 var p4 = Variable.of("p4");
195 var x = Variable.of("x", Integer.class);
196 var subQuery = Dnf.builder("SubQuery")
197 .parameters(p1, p2, p3, p4)
198 .clause(
199 personView.call(p1),
200 symbolView.call(p1, p2, p3, p4)
201 )
202 .clause(
203 personView.call(p2),
204 symbolView.call(p1, p2, p3, p4)
205 )
206 .build();
207 var query = Query.builder("Diagonal")
208 .parameter(p1)
209 .output(x)
210 .clause(
211 personView.call(p1),
212 x.assign(subQuery.count(p1, p1, p2, p2))
213 )
214 .build();
215
216 var store = ModelStore.builder()
217 .symbols(person, symbol)
218 .with(ViatraModelQuery.ADAPTER)
219 .defaultHint(hint)
220 .queries(query)
221 .build();
222
223 var model = store.createEmptyModel();
224 var personInterpretation = model.getInterpretation(person);
225 var symbolInterpretation = model.getInterpretation(symbol);
226 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
227 var queryResultSet = queryEngine.getResultSet(query);
228
229 personInterpretation.put(Tuple.of(0), true);
230 personInterpretation.put(Tuple.of(1), true);
231 personInterpretation.put(Tuple.of(2), true);
232
233 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), true);
234 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), true);
235 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), true);
236 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), true);
237 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), true);
238
239 queryEngine.flushChanges();
240 assertNullableResults(Map.of(
241 Tuple.of(0), Optional.of(2),
242 Tuple.of(1), Optional.of(0),
243 Tuple.of(2), Optional.of(0),
244 Tuple.of(3), Optional.empty()
245 ), queryResultSet);
246 }
247
248 @QueryEngineTest
249 void inputKeyAggregationTest(QueryEvaluationHint hint) {
250 var person = new Symbol<>("Person", 1, Boolean.class, false);
251 var symbol = new Symbol<>("symbol", 4, Integer.class, null);
252 var personView = new KeyOnlyRelationView<>(person);
253 var symbolView = new FunctionalRelationView<>(symbol);
254
255 var p1 = Variable.of("p1");
256 var p2 = Variable.of("p2");
257 var x = Variable.of("x", Integer.class);
258 var y = Variable.of("y", Integer.class);
259 var query = Query.builder("Diagonal")
260 .parameter(p1)
261 .output(x)
262 .clause(
263 personView.call(p1),
264 x.assign(symbolView.aggregate(y, INT_SUM, p1, p1, p2, p2, y))
265 )
266 .build();
267
268 var store = ModelStore.builder()
269 .symbols(person, symbol)
270 .with(ViatraModelQuery.ADAPTER)
271 .defaultHint(hint)
272 .queries(query)
273 .build();
274
275 var model = store.createEmptyModel();
276 var personInterpretation = model.getInterpretation(person);
277 var symbolInterpretation = model.getInterpretation(symbol);
278 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
279 var queryResultSet = queryEngine.getResultSet(query);
280
281 personInterpretation.put(Tuple.of(0), true);
282 personInterpretation.put(Tuple.of(1), true);
283 personInterpretation.put(Tuple.of(2), true);
284
285 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1);
286 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2);
287 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10);
288 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11);
289 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12);
290
291 queryEngine.flushChanges();
292 assertNullableResults(Map.of(
293 Tuple.of(0), Optional.of(3),
294 Tuple.of(1), Optional.of(0),
295 Tuple.of(2), Optional.of(0),
296 Tuple.of(3), Optional.empty()
297 ), queryResultSet);
298 }
299
300 @QueryEngineTest
301 void subQueryAggregationTest(QueryEvaluationHint hint) {
302 var person = new Symbol<>("Person", 1, Boolean.class, false);
303 var symbol = new Symbol<>("symbol", 4, Integer.class, null);
304 var personView = new KeyOnlyRelationView<>(person);
305 var symbolView = new FunctionalRelationView<>(symbol);
306
307 var p1 = Variable.of("p1");
308 var p2 = Variable.of("p2");
309 var p3 = Variable.of("p3");
310 var p4 = Variable.of("p4");
311 var x = Variable.of("x", Integer.class);
312 var y = Variable.of("y", Integer.class);
313 var z = Variable.of("z", Integer.class);
314 var subQuery = Dnf.builder("SubQuery")
315 .parameters(p1, p2, p3, p4, x, y)
316 .clause(
317 personView.call(p1),
318 symbolView.call(p1, p2, p3, p4, x),
319 y.assign(x)
320 )
321 .clause(
322 personView.call(p2),
323 symbolView.call(p1, p2, p3, p4, x),
324 y.assign(x)
325 )
326 .build();
327 var query = Query.builder("Diagonal")
328 .parameter(p1)
329 .output(x)
330 .clause(
331 personView.call(p1),
332 x.assign(subQuery.aggregate(z, INT_SUM, p1, p1, p2, p2, z, z))
333 )
334 .build();
335
336 var store = ModelStore.builder()
337 .symbols(person, symbol)
338 .with(ViatraModelQuery.ADAPTER)
339 .defaultHint(hint)
340 .queries(query)
341 .build();
342
343 var model = store.createEmptyModel();
344 var personInterpretation = model.getInterpretation(person);
345 var symbolInterpretation = model.getInterpretation(symbol);
346 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
347 var queryResultSet = queryEngine.getResultSet(query);
348
349 personInterpretation.put(Tuple.of(0), true);
350 personInterpretation.put(Tuple.of(1), true);
351 personInterpretation.put(Tuple.of(2), true);
352
353 symbolInterpretation.put(Tuple.of(0, 0, 1, 1), 1);
354 symbolInterpretation.put(Tuple.of(0, 0, 2, 2), 2);
355 symbolInterpretation.put(Tuple.of(0, 0, 1, 2), 10);
356 symbolInterpretation.put(Tuple.of(1, 1, 0, 1), 11);
357 symbolInterpretation.put(Tuple.of(1, 2, 1, 1), 12);
358
359 queryEngine.flushChanges();
360 assertNullableResults(Map.of(
361 Tuple.of(0), Optional.of(3),
362 Tuple.of(1), Optional.of(0),
363 Tuple.of(2), Optional.of(0),
364 Tuple.of(3), Optional.empty()
365 ), queryResultSet);
366 }
367
368 @QueryEngineTest
369 void inputKeyTransitiveTest(QueryEvaluationHint hint) {
370 var person = new Symbol<>("Person", 1, Boolean.class, false);
371 var symbol = new Symbol<>("symbol", 2, Boolean.class, false);
372 var personView = new KeyOnlyRelationView<>(person);
373 var symbolView = new KeyOnlyRelationView<>(symbol);
374
375 var p1 = Variable.of("p1");
376 var query = Query.builder("Diagonal")
377 .parameter(p1)
378 .clause(
379 personView.call(p1),
380 symbolView.callTransitive(p1, p1)
381 )
382 .build();
383
384 var store = ModelStore.builder()
385 .symbols(person, symbol)
386 .with(ViatraModelQuery.ADAPTER)
387 .defaultHint(hint)
388 .queries(query)
389 .build();
390
391 var model = store.createEmptyModel();
392 var personInterpretation = model.getInterpretation(person);
393 var symbolInterpretation = model.getInterpretation(symbol);
394 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
395 var queryResultSet = queryEngine.getResultSet(query);
396
397 personInterpretation.put(Tuple.of(0), true);
398 personInterpretation.put(Tuple.of(1), true);
399 personInterpretation.put(Tuple.of(2), true);
400
401 symbolInterpretation.put(Tuple.of(0, 0), true);
402 symbolInterpretation.put(Tuple.of(0, 1), true);
403 symbolInterpretation.put(Tuple.of(1, 2), true);
404
405 queryEngine.flushChanges();
406 assertResults(Map.of(
407 Tuple.of(0), true,
408 Tuple.of(1), false,
409 Tuple.of(2), false,
410 Tuple.of(3), false
411 ), queryResultSet);
412 }
413
414 @QueryEngineTest
415 void subQueryTransitiveTest(QueryEvaluationHint hint) {
416 var person = new Symbol<>("Person", 1, Boolean.class, false);
417 var symbol = new Symbol<>("symbol", 2, Boolean.class, false);
418 var personView = new KeyOnlyRelationView<>(person);
419 var symbolView = new KeyOnlyRelationView<>(symbol);
420
421 var p1 = Variable.of("p1");
422 var p2 = Variable.of("p2");
423 var subQuery = Dnf.builder("SubQuery")
424 .parameters(p1, p2)
425 .clause(
426 personView.call(p1),
427 symbolView.call(p1, p2)
428 )
429 .clause(
430 personView.call(p2),
431 symbolView.call(p1, p2)
432 )
433 .build();
434 var query = Query.builder("Diagonal")
435 .parameter(p1)
436 .clause(
437 personView.call(p1),
438 subQuery.callTransitive(p1, p1)
439 )
440 .build();
441
442 var store = ModelStore.builder()
443 .symbols(person, symbol)
444 .with(ViatraModelQuery.ADAPTER)
445 .defaultHint(hint)
446 .queries(query)
447 .build();
448
449 var model = store.createEmptyModel();
450 var personInterpretation = model.getInterpretation(person);
451 var symbolInterpretation = model.getInterpretation(symbol);
452 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
453 var queryResultSet = queryEngine.getResultSet(query);
454
455 personInterpretation.put(Tuple.of(0), true);
456 personInterpretation.put(Tuple.of(1), true);
457 personInterpretation.put(Tuple.of(2), true);
458
459 symbolInterpretation.put(Tuple.of(0, 0), true);
460 symbolInterpretation.put(Tuple.of(0, 1), true);
461 symbolInterpretation.put(Tuple.of(1, 2), true);
462
463 queryEngine.flushChanges();
464 assertResults(Map.of(
465 Tuple.of(0), true,
466 Tuple.of(1), false,
467 Tuple.of(2), false,
468 Tuple.of(3), false
469 ), queryResultSet);
470 }
471}
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 @@
1package tools.refinery.store.query.viatra;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import tools.refinery.store.map.Cursor;
5import tools.refinery.store.model.ModelStore;
6import tools.refinery.store.query.ModelQuery;
7import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.viatra.tests.QueryEngineTest;
11import tools.refinery.store.query.view.FilteredRelationView;
12import tools.refinery.store.query.view.FunctionalRelationView;
13import tools.refinery.store.query.view.KeyOnlyRelationView;
14import tools.refinery.store.representation.Symbol;
15import tools.refinery.store.representation.TruthValue;
16import tools.refinery.store.tuple.Tuple;
17
18import java.util.Map;
19import java.util.Optional;
20
21import static org.hamcrest.MatcherAssert.assertThat;
22import static org.hamcrest.Matchers.is;
23import static org.hamcrest.Matchers.nullValue;
24import static org.junit.jupiter.api.Assertions.assertAll;
25import static org.junit.jupiter.api.Assertions.assertThrows;
26import static tools.refinery.store.query.literal.Literals.assume;
27import static tools.refinery.store.query.term.int_.IntTerms.*;
28import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
29import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
30
31class FunctionalQueryTest {
32 @QueryEngineTest
33 void inputKeyTest(QueryEvaluationHint hint) {
34 var person = new Symbol<>("Person", 1, Boolean.class, false);
35 var age = new Symbol<>("age", 1, Integer.class, null);
36 var personView = new KeyOnlyRelationView<>(person);
37 var ageView = new FunctionalRelationView<>(age);
38
39 var p1 = Variable.of("p1");
40 var x = Variable.of("x", Integer.class);
41 var query = Query.builder("InputKey")
42 .parameter(p1)
43 .output(x)
44 .clause(
45 personView.call(p1),
46 ageView.call(p1, x)
47 )
48 .build();
49
50 var store = ModelStore.builder()
51 .symbols(person, age)
52 .with(ViatraModelQuery.ADAPTER)
53 .defaultHint(hint)
54 .queries(query)
55 .build();
56
57 var model = store.createEmptyModel();
58 var personInterpretation = model.getInterpretation(person);
59 var ageInterpretation = model.getInterpretation(age);
60 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
61 var queryResultSet = queryEngine.getResultSet(query);
62
63 personInterpretation.put(Tuple.of(0), true);
64 personInterpretation.put(Tuple.of(1), true);
65
66 ageInterpretation.put(Tuple.of(0), 12);
67 ageInterpretation.put(Tuple.of(1), 24);
68 ageInterpretation.put(Tuple.of(2), 36);
69
70 queryEngine.flushChanges();
71 assertNullableResults(Map.of(
72 Tuple.of(0), Optional.of(12),
73 Tuple.of(1), Optional.of(24),
74 Tuple.of(2), Optional.empty()
75 ), queryResultSet);
76 }
77
78 @QueryEngineTest
79 void predicateTest(QueryEvaluationHint hint) {
80 var person = new Symbol<>("Person", 1, Boolean.class, false);
81 var age = new Symbol<>("age", 1, Integer.class, null);
82 var personView = new KeyOnlyRelationView<>(person);
83 var ageView = new FunctionalRelationView<>(age);
84
85 var p1 = Variable.of("p1");
86 var x = Variable.of("x", Integer.class);
87 var subQuery = Dnf.builder("SubQuery")
88 .parameters(p1, x)
89 .clause(
90 personView.call(p1),
91 ageView.call(p1, x)
92 )
93 .build();
94 var query = Query.builder("Predicate")
95 .parameter(p1)
96 .output(x)
97 .clause(
98 personView.call(p1),
99 subQuery.call(p1, x)
100 )
101 .build();
102
103 var store = ModelStore.builder()
104 .symbols(person, age)
105 .with(ViatraModelQuery.ADAPTER)
106 .defaultHint(hint)
107 .queries(query)
108 .build();
109
110 var model = store.createEmptyModel();
111 var personInterpretation = model.getInterpretation(person);
112 var ageInterpretation = model.getInterpretation(age);
113 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
114 var queryResultSet = queryEngine.getResultSet(query);
115
116 personInterpretation.put(Tuple.of(0), true);
117 personInterpretation.put(Tuple.of(1), true);
118
119 ageInterpretation.put(Tuple.of(0), 12);
120 ageInterpretation.put(Tuple.of(1), 24);
121 ageInterpretation.put(Tuple.of(2), 36);
122
123 queryEngine.flushChanges();
124 assertNullableResults(Map.of(
125 Tuple.of(0), Optional.of(12),
126 Tuple.of(1), Optional.of(24),
127 Tuple.of(2), Optional.empty()
128 ), queryResultSet);
129 }
130
131 @QueryEngineTest
132 void computationTest(QueryEvaluationHint hint) {
133 var person = new Symbol<>("Person", 1, Boolean.class, false);
134 var age = new Symbol<>("age", 1, Integer.class, null);
135 var personView = new KeyOnlyRelationView<>(person);
136 var ageView = new FunctionalRelationView<>(age);
137
138 var p1 = Variable.of("p1");
139 var x = Variable.of("x", Integer.class);
140 var y = Variable.of("y", Integer.class);
141 var query = Query.builder("Computation")
142 .parameter(p1)
143 .output(y)
144 .clause(
145 personView.call(p1),
146 ageView.call(p1, x),
147 y.assign(mul(x, constant(7)))
148 )
149 .build();
150
151 var store = ModelStore.builder()
152 .symbols(person, age)
153 .with(ViatraModelQuery.ADAPTER)
154 .defaultHint(hint)
155 .queries(query)
156 .build();
157
158 var model = store.createEmptyModel();
159 var personInterpretation = model.getInterpretation(person);
160 var ageInterpretation = model.getInterpretation(age);
161 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
162 var queryResultSet = queryEngine.getResultSet(query);
163
164 personInterpretation.put(Tuple.of(0), true);
165 personInterpretation.put(Tuple.of(1), true);
166
167 ageInterpretation.put(Tuple.of(0), 12);
168 ageInterpretation.put(Tuple.of(1), 24);
169
170 queryEngine.flushChanges();
171 assertNullableResults(Map.of(
172 Tuple.of(0), Optional.of(84),
173 Tuple.of(1), Optional.of(168),
174 Tuple.of(2), Optional.empty()
175 ), queryResultSet);
176 }
177
178 @QueryEngineTest
179 void inputKeyCountTest(QueryEvaluationHint hint) {
180 var person = new Symbol<>("Person", 1, Boolean.class, false);
181 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
182 var personView = new KeyOnlyRelationView<>(person);
183 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
184
185 var p1 = Variable.of("p1");
186 var p2 = Variable.of("p2");
187 var x = Variable.of("x", Integer.class);
188 var query = Query.builder("Count")
189 .parameter(p1)
190 .output(x)
191 .clause(
192 personView.call(p1),
193 x.assign(friendMustView.count(p1, p2))
194 )
195 .build();
196
197 var store = ModelStore.builder()
198 .symbols(person, friend)
199 .with(ViatraModelQuery.ADAPTER)
200 .defaultHint(hint)
201 .queries(query)
202 .build();
203
204 var model = store.createEmptyModel();
205 var personInterpretation = model.getInterpretation(person);
206 var friendInterpretation = model.getInterpretation(friend);
207 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
208 var queryResultSet = queryEngine.getResultSet(query);
209
210 personInterpretation.put(Tuple.of(0), true);
211 personInterpretation.put(Tuple.of(1), true);
212 personInterpretation.put(Tuple.of(2), true);
213
214 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
215 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
216 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
217
218 queryEngine.flushChanges();
219 assertNullableResults(Map.of(
220 Tuple.of(0), Optional.of(1),
221 Tuple.of(1), Optional.of(2),
222 Tuple.of(2), Optional.of(0),
223 Tuple.of(3), Optional.empty()
224 ), queryResultSet);
225 }
226
227 @QueryEngineTest
228 void predicateCountTest(QueryEvaluationHint hint) {
229 var person = new Symbol<>("Person", 1, Boolean.class, false);
230 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
231 var personView = new KeyOnlyRelationView<>(person);
232 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
233
234 var p1 = Variable.of("p1");
235 var p2 = Variable.of("p2");
236 var x = Variable.of("x", Integer.class);
237 var subQuery = Dnf.builder("SubQuery")
238 .parameters(p1, p2)
239 .clause(
240 personView.call(p1),
241 personView.call(p2),
242 friendMustView.call(p1, p2)
243 )
244 .build();
245 var query = Query.builder("Count")
246 .parameter(p1)
247 .output(x)
248 .clause(
249 personView.call(p1),
250 x.assign(subQuery.count(p1, p2))
251 )
252 .build();
253
254 var store = ModelStore.builder()
255 .symbols(person, friend)
256 .with(ViatraModelQuery.ADAPTER)
257 .defaultHint(hint)
258 .queries(query)
259 .build();
260
261 var model = store.createEmptyModel();
262 var personInterpretation = model.getInterpretation(person);
263 var friendInterpretation = model.getInterpretation(friend);
264 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
265 var queryResultSet = queryEngine.getResultSet(query);
266
267 personInterpretation.put(Tuple.of(0), true);
268 personInterpretation.put(Tuple.of(1), true);
269 personInterpretation.put(Tuple.of(2), true);
270
271 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
272 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
273 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
274
275 queryEngine.flushChanges();
276 assertNullableResults(Map.of(
277 Tuple.of(0), Optional.of(1),
278 Tuple.of(1), Optional.of(2),
279 Tuple.of(2), Optional.of(0),
280 Tuple.of(3), Optional.empty()
281 ), queryResultSet);
282 }
283
284 @QueryEngineTest
285 void inputKeyAggregationTest(QueryEvaluationHint hint) {
286 var age = new Symbol<>("age", 1, Integer.class, null);
287 var ageView = new FunctionalRelationView<>(age);
288
289 var p1 = Variable.of("p1");
290 var x = Variable.of("x", Integer.class);
291 var y = Variable.of("y", Integer.class);
292 var query = Query.builder("Aggregate")
293 .output(x)
294 .clause(
295 x.assign(ageView.aggregate(y, INT_SUM, p1, y))
296 )
297 .build();
298
299 var store = ModelStore.builder()
300 .symbols(age)
301 .with(ViatraModelQuery.ADAPTER)
302 .defaultHint(hint)
303 .queries(query)
304 .build();
305
306 var model = store.createEmptyModel();
307 var ageInterpretation = model.getInterpretation(age);
308 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
309 var queryResultSet = queryEngine.getResultSet(query);
310
311 ageInterpretation.put(Tuple.of(0), 12);
312 ageInterpretation.put(Tuple.of(1), 24);
313
314 queryEngine.flushChanges();
315 assertResults(Map.of(Tuple.of(), 36), queryResultSet);
316 }
317
318 @QueryEngineTest
319 void predicateAggregationTest(QueryEvaluationHint hint) {
320 var person = new Symbol<>("Person", 1, Boolean.class, false);
321 var age = new Symbol<>("age", 1, Integer.class, null);
322 var personView = new KeyOnlyRelationView<>(person);
323 var ageView = new FunctionalRelationView<>(age);
324
325 var p1 = Variable.of("p1");
326 var x = Variable.of("x", Integer.class);
327 var y = Variable.of("y", Integer.class);
328 var subQuery = Dnf.builder("SubQuery")
329 .parameters(p1, x)
330 .clause(
331 personView.call(p1),
332 ageView.call(p1, x)
333 )
334 .build();
335 var query = Query.builder("Aggregate")
336 .output(x)
337 .clause(
338 x.assign(subQuery.aggregate(y, INT_SUM, p1, y))
339 )
340 .build();
341
342 var store = ModelStore.builder()
343 .symbols(person, age)
344 .with(ViatraModelQuery.ADAPTER)
345 .defaultHint(hint)
346 .queries(query)
347 .build();
348
349 var model = store.createEmptyModel();
350 var personInterpretation = model.getInterpretation(person);
351 var ageInterpretation = model.getInterpretation(age);
352 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
353 var queryResultSet = queryEngine.getResultSet(query);
354
355 personInterpretation.put(Tuple.of(0), true);
356 personInterpretation.put(Tuple.of(1), true);
357
358 ageInterpretation.put(Tuple.of(0), 12);
359 ageInterpretation.put(Tuple.of(1), 24);
360
361 queryEngine.flushChanges();
362 assertResults(Map.of(Tuple.of(), 36), queryResultSet);
363 }
364
365 @QueryEngineTest
366 void extremeValueTest(QueryEvaluationHint hint) {
367 var person = new Symbol<>("Person", 1, Boolean.class, false);
368 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
369 var personView = new KeyOnlyRelationView<>(person);
370 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
371
372 var p1 = Variable.of("p1");
373 var p2 = Variable.of("p2");
374 var x = Variable.of("x", Integer.class);
375 var y = Variable.of("y", Integer.class);
376 var subQuery = Dnf.builder("SubQuery")
377 .parameters(p1, x)
378 .clause(
379 personView.call(p1),
380 x.assign(friendMustView.count(p1, p2))
381 )
382 .build();
383 var minQuery = Query.builder("Min")
384 .output(x)
385 .clause(
386 x.assign(subQuery.aggregate(y, INT_MIN, p1, y))
387 )
388 .build();
389 var maxQuery = Query.builder("Max")
390 .output(x)
391 .clause(
392 x.assign(subQuery.aggregate(y, INT_MAX, p1, y))
393 )
394 .build();
395
396 var store = ModelStore.builder()
397 .symbols(person, friend)
398 .with(ViatraModelQuery.ADAPTER)
399 .defaultHint(hint)
400 .queries(minQuery, maxQuery)
401 .build();
402
403 var model = store.createEmptyModel();
404 var personInterpretation = model.getInterpretation(person);
405 var friendInterpretation = model.getInterpretation(friend);
406 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
407 var minResultSet = queryEngine.getResultSet(minQuery);
408 var maxResultSet = queryEngine.getResultSet(maxQuery);
409
410 assertResults(Map.of(Tuple.of(), Integer.MAX_VALUE), minResultSet);
411 assertResults(Map.of(Tuple.of(), Integer.MIN_VALUE), maxResultSet);
412
413 personInterpretation.put(Tuple.of(0), true);
414 personInterpretation.put(Tuple.of(1), true);
415 personInterpretation.put(Tuple.of(2), true);
416
417 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
418 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
419 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
420
421 queryEngine.flushChanges();
422 assertResults(Map.of(Tuple.of(), 0), minResultSet);
423 assertResults(Map.of(Tuple.of(), 2), maxResultSet);
424
425 friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE);
426 friendInterpretation.put(Tuple.of(2, 1), TruthValue.TRUE);
427
428 queryEngine.flushChanges();
429 assertResults(Map.of(Tuple.of(), 1), minResultSet);
430 assertResults(Map.of(Tuple.of(), 2), maxResultSet);
431
432 friendInterpretation.put(Tuple.of(0, 1), TruthValue.FALSE);
433 friendInterpretation.put(Tuple.of(1, 0), TruthValue.FALSE);
434 friendInterpretation.put(Tuple.of(2, 0), TruthValue.FALSE);
435
436 queryEngine.flushChanges();
437 assertResults(Map.of(Tuple.of(), 0), minResultSet);
438 assertResults(Map.of(Tuple.of(), 1), maxResultSet);
439 }
440
441 @QueryEngineTest
442 void invalidComputationTest(QueryEvaluationHint hint) {
443 var person = new Symbol<>("Person", 1, Boolean.class, false);
444 var age = new Symbol<>("age", 1, Integer.class, null);
445 var personView = new KeyOnlyRelationView<>(person);
446 var ageView = new FunctionalRelationView<>(age);
447
448 var p1 = Variable.of("p1");
449 var x = Variable.of("x", Integer.class);
450 var y = Variable.of("y", Integer.class);
451 var query = Query.builder("InvalidComputation")
452 .parameter(p1)
453 .output(y)
454 .clause(
455 personView.call(p1),
456 ageView.call(p1, x),
457 y.assign(div(constant(120), x))
458 )
459 .build();
460
461 var store = ModelStore.builder()
462 .symbols(person, age)
463 .with(ViatraModelQuery.ADAPTER)
464 .defaultHint(hint)
465 .queries(query)
466 .build();
467
468 var model = store.createEmptyModel();
469 var personInterpretation = model.getInterpretation(person);
470 var ageInterpretation = model.getInterpretation(age);
471 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
472 var queryResultSet = queryEngine.getResultSet(query);
473
474 personInterpretation.put(Tuple.of(0), true);
475 personInterpretation.put(Tuple.of(1), true);
476
477 ageInterpretation.put(Tuple.of(0), 0);
478 ageInterpretation.put(Tuple.of(1), 30);
479
480 queryEngine.flushChanges();
481 assertNullableResults(Map.of(
482 Tuple.of(0), Optional.empty(),
483 Tuple.of(1), Optional.of(4),
484 Tuple.of(2), Optional.empty()
485 ), queryResultSet);
486 }
487
488 @QueryEngineTest
489 void invalidAssumeTest(QueryEvaluationHint hint) {
490 var person = new Symbol<>("Person", 1, Boolean.class, false);
491 var age = new Symbol<>("age", 1, Integer.class, null);
492 var personView = new KeyOnlyRelationView<>(person);
493 var ageView = new FunctionalRelationView<>(age);
494
495 var p1 = Variable.of("p1");
496 var x = Variable.of("x", Integer.class);
497 var query = Query.builder("InvalidComputation")
498 .parameter(p1)
499 .clause(
500 personView.call(p1),
501 ageView.call(p1, x),
502 assume(lessEq(div(constant(120), x), constant(5)))
503 )
504 .build();
505
506 var store = ModelStore.builder()
507 .symbols(person, age)
508 .with(ViatraModelQuery.ADAPTER)
509 .defaultHint(hint)
510 .queries(query)
511 .build();
512
513 var model = store.createEmptyModel();
514 var personInterpretation = model.getInterpretation(person);
515 var ageInterpretation = model.getInterpretation(age);
516 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
517 var queryResultSet = queryEngine.getResultSet(query);
518
519 personInterpretation.put(Tuple.of(0), true);
520 personInterpretation.put(Tuple.of(1), true);
521 personInterpretation.put(Tuple.of(2), true);
522
523 ageInterpretation.put(Tuple.of(0), 0);
524 ageInterpretation.put(Tuple.of(1), 30);
525 ageInterpretation.put(Tuple.of(2), 20);
526
527 queryEngine.flushChanges();
528 assertResults(Map.of(
529 Tuple.of(0), false,
530 Tuple.of(1), true,
531 Tuple.of(2), false,
532 Tuple.of(3), false
533 ), queryResultSet);
534 }
535
536 @QueryEngineTest
537 void notFunctionalTest(QueryEvaluationHint hint) {
538 var person = new Symbol<>("Person", 1, Boolean.class, false);
539 var age = new Symbol<>("age", 1, Integer.class, null);
540 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
541 var personView = new KeyOnlyRelationView<>(person);
542 var ageView = new FunctionalRelationView<>(age);
543 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
544
545 var p1 = Variable.of("p1");
546 var p2 = Variable.of("p2");
547 var x = Variable.of("x", Integer.class);
548 var query = Query.builder("NotFunctional")
549 .parameter(p1)
550 .output(x)
551 .clause(
552 personView.call(p1),
553 friendMustView.call(p1, p2),
554 ageView.call(p2, x)
555 )
556 .build();
557
558 var store = ModelStore.builder()
559 .symbols(person, age, friend)
560 .with(ViatraModelQuery.ADAPTER)
561 .defaultHint(hint)
562 .query(query)
563 .build();
564
565 var model = store.createEmptyModel();
566 var personInterpretation = model.getInterpretation(person);
567 var ageInterpretation = model.getInterpretation(age);
568 var friendInterpretation = model.getInterpretation(friend);
569 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
570 var queryResultSet = queryEngine.getResultSet(query);
571
572 personInterpretation.put(Tuple.of(0), true);
573 personInterpretation.put(Tuple.of(1), true);
574 personInterpretation.put(Tuple.of(2), true);
575
576 ageInterpretation.put(Tuple.of(0), 24);
577 ageInterpretation.put(Tuple.of(1), 30);
578 ageInterpretation.put(Tuple.of(2), 36);
579
580 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
581 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
582 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
583
584 queryEngine.flushChanges();
585 var invalidTuple = Tuple.of(1);
586 var cursor = queryResultSet.getAll();
587 assertAll(
588 () -> assertThat("value for key 0", queryResultSet.get(Tuple.of(0)), is(30)),
589 () -> assertThrows(IllegalStateException.class, () -> queryResultSet.get(invalidTuple),
590 "multiple values for key 1"),
591 () -> assertThat("value for key 2", queryResultSet.get(Tuple.of(2)), is(nullValue())),
592 () -> assertThat("value for key 3", queryResultSet.get(Tuple.of(3)), is(nullValue()))
593 );
594 if (hint.getQueryBackendRequirementType() != QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH) {
595 // Local search doesn't support throwing an error on multiple function return values.
596 assertThat("results size", queryResultSet.size(), is(2));
597 assertThrows(IllegalStateException.class, () -> enumerateValues(cursor), "move cursor");
598 }
599 }
600
601 private static void enumerateValues(Cursor<?, ?> cursor) {
602 //noinspection StatementWithEmptyBody
603 while (cursor.move()) {
604 // Nothing do, just let the cursor move through the result set.
605 }
606 }
607}
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 6a37b54a..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,41 +1,47 @@
1package tools.refinery.store.query.viatra; 1package tools.refinery.store.query.viatra;
2 2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
3import org.junit.jupiter.api.Test; 4import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 5import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF;
6import tools.refinery.store.query.ModelQuery; 6import tools.refinery.store.query.ModelQuery;
7import tools.refinery.store.query.Variable; 7import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.atom.*; 8import tools.refinery.store.query.dnf.Query;
9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.viatra.tests.QueryEngineTest;
9import tools.refinery.store.query.view.FilteredRelationView; 11import tools.refinery.store.query.view.FilteredRelationView;
12import tools.refinery.store.query.view.FunctionalRelationView;
10import tools.refinery.store.query.view.KeyOnlyRelationView; 13import tools.refinery.store.query.view.KeyOnlyRelationView;
11import tools.refinery.store.representation.Symbol; 14import tools.refinery.store.representation.Symbol;
12import tools.refinery.store.representation.TruthValue; 15import tools.refinery.store.representation.TruthValue;
13import tools.refinery.store.tuple.Tuple; 16import tools.refinery.store.tuple.Tuple;
14import tools.refinery.store.tuple.TupleLike;
15 17
16import java.util.HashSet; 18import java.util.Map;
17import java.util.Set;
18import java.util.stream.Stream;
19 19
20import static org.junit.jupiter.api.Assertions.assertEquals; 20import static org.junit.jupiter.api.Assertions.assertThrows;
21import static tools.refinery.store.query.literal.Literals.assume;
22import static tools.refinery.store.query.literal.Literals.not;
23import static tools.refinery.store.query.term.int_.IntTerms.constant;
24import static tools.refinery.store.query.term.int_.IntTerms.greaterEq;
25import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
21 26
22class QueryTest { 27class QueryTest {
23 @Test 28 @QueryEngineTest
24 void typeConstraintTest() { 29 void typeConstraintTest(QueryEvaluationHint hint) {
25 var person = new Symbol<>("Person", 1, Boolean.class, false); 30 var person = new Symbol<>("Person", 1, Boolean.class, false);
26 var asset = new Symbol<>("Asset", 1, Boolean.class, false); 31 var asset = new Symbol<>("Asset", 1, Boolean.class, false);
27 var personView = new KeyOnlyRelationView<>(person); 32 var personView = new KeyOnlyRelationView<>(person);
28 33
29 var p1 = new Variable("p1"); 34 var p1 = Variable.of("p1");
30 var predicate = DNF.builder("TypeConstraint") 35 var predicate = Query.builder("TypeConstraint")
31 .parameters(p1) 36 .parameters(p1)
32 .clause(new RelationViewAtom(personView, p1)) 37 .clause(personView.call(p1))
33 .build(); 38 .build();
34 39
35 var store = ModelStore.builder() 40 var store = ModelStore.builder()
36 .symbols(person, asset) 41 .symbols(person, asset)
37 .with(ViatraModelQuery.ADAPTER) 42 .with(ViatraModelQuery.ADAPTER)
38 .queries(predicate) 43 .defaultHint(hint)
44 .query(predicate)
39 .build(); 45 .build();
40 46
41 var model = store.createEmptyModel(); 47 var model = store.createEmptyModel();
@@ -51,31 +57,35 @@ class QueryTest {
51 assetInterpretation.put(Tuple.of(2), true); 57 assetInterpretation.put(Tuple.of(2), true);
52 58
53 queryEngine.flushChanges(); 59 queryEngine.flushChanges();
54 assertEquals(2, predicateResultSet.countResults()); 60 assertResults(Map.of(
55 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); 61 Tuple.of(0), true,
62 Tuple.of(1), true,
63 Tuple.of(2), false
64 ), predicateResultSet);
56 } 65 }
57 66
58 @Test 67 @QueryEngineTest
59 void relationConstraintTest() { 68 void relationConstraintTest(QueryEvaluationHint hint) {
60 var person = new Symbol<>("Person", 1, Boolean.class, false); 69 var person = new Symbol<>("Person", 1, Boolean.class, false);
61 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 70 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
62 var personView = new KeyOnlyRelationView<>(person); 71 var personView = new KeyOnlyRelationView<>(person);
63 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 72 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
64 73
65 var p1 = new Variable("p1"); 74 var p1 = Variable.of("p1");
66 var p2 = new Variable("p2"); 75 var p2 = Variable.of("p2");
67 var predicate = DNF.builder("RelationConstraint") 76 var predicate = Query.builder("RelationConstraint")
68 .parameters(p1, p2) 77 .parameters(p1, p2)
69 .clause( 78 .clause(
70 new RelationViewAtom(personView, p1), 79 personView.call(p1),
71 new RelationViewAtom(personView, p2), 80 personView.call(p2),
72 new RelationViewAtom(friendMustView, p1, p2) 81 friendMustView.call(p1, p2)
73 ) 82 )
74 .build(); 83 .build();
75 84
76 var store = ModelStore.builder() 85 var store = ModelStore.builder()
77 .symbols(person, friend) 86 .symbols(person, friend)
78 .with(ViatraModelQuery.ADAPTER) 87 .with(ViatraModelQuery.ADAPTER)
88 .defaultHint(hint)
79 .queries(predicate) 89 .queries(predicate)
80 .build(); 90 .build();
81 91
@@ -85,8 +95,6 @@ class QueryTest {
85 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 95 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
86 var predicateResultSet = queryEngine.getResultSet(predicate); 96 var predicateResultSet = queryEngine.getResultSet(predicate);
87 97
88 assertEquals(0, predicateResultSet.countResults());
89
90 personInterpretation.put(Tuple.of(0), true); 98 personInterpretation.put(Tuple.of(0), true);
91 personInterpretation.put(Tuple.of(1), true); 99 personInterpretation.put(Tuple.of(1), true);
92 personInterpretation.put(Tuple.of(2), true); 100 personInterpretation.put(Tuple.of(2), true);
@@ -94,90 +102,39 @@ class QueryTest {
94 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); 102 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
95 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); 103 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
96 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 104 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
97 105 friendInterpretation.put(Tuple.of(1, 3), TruthValue.TRUE);
98 assertEquals(0, predicateResultSet.countResults());
99 106
100 queryEngine.flushChanges(); 107 queryEngine.flushChanges();
101 assertEquals(3, predicateResultSet.countResults()); 108 assertResults(Map.of(
102 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(1, 2))); 109 Tuple.of(0, 1), true,
110 Tuple.of(1, 0), true,
111 Tuple.of(1, 2), true,
112 Tuple.of(2, 1), false
113 ), predicateResultSet);
103 } 114 }
104 115
105 @Test 116 @QueryEngineTest
106 void andTest() { 117 void existTest(QueryEvaluationHint hint) {
107 var person = new Symbol<>("Person", 1, Boolean.class, false); 118 var person = new Symbol<>("Person", 1, Boolean.class, false);
108 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 119 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
109 var personView = new KeyOnlyRelationView<>(person); 120 var personView = new KeyOnlyRelationView<>(person);
110 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 121 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
111 122
112 var p1 = new Variable("p1"); 123 var p1 = Variable.of("p1");
113 var p2 = new Variable("p2"); 124 var p2 = Variable.of("p2");
114 var predicate = DNF.builder("RelationConstraint") 125 var predicate = Query.builder("RelationConstraint")
115 .parameters(p1, p2)
116 .clause(
117 new RelationViewAtom(personView, p1),
118 new RelationViewAtom(personView, p2),
119 new RelationViewAtom(friendMustView, p1, p2),
120 new RelationViewAtom(friendMustView, p2, p1)
121 )
122 .build();
123
124 var store = ModelStore.builder()
125 .symbols(person, friend)
126 .with(ViatraModelQuery.ADAPTER)
127 .queries(predicate)
128 .build();
129
130 var model = store.createEmptyModel();
131 var personInterpretation = model.getInterpretation(person);
132 var friendInterpretation = model.getInterpretation(friend);
133 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
134 var predicateResultSet = queryEngine.getResultSet(predicate);
135
136 assertEquals(0, predicateResultSet.countResults());
137
138 personInterpretation.put(Tuple.of(0), true);
139 personInterpretation.put(Tuple.of(1), true);
140 personInterpretation.put(Tuple.of(2), true);
141
142 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
143 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
144
145 queryEngine.flushChanges();
146 assertEquals(0, predicateResultSet.countResults());
147
148 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
149 queryEngine.flushChanges();
150 assertEquals(2, predicateResultSet.countResults());
151 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0)));
152
153 friendInterpretation.put(Tuple.of(2, 0), TruthValue.TRUE);
154 queryEngine.flushChanges();
155 assertEquals(4, predicateResultSet.countResults());
156 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(1, 0), Tuple.of(0, 2),
157 Tuple.of(2, 0)));
158 }
159
160 @Test
161 void existTest() {
162 var person = new Symbol<>("Person", 1, Boolean.class, false);
163 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
164 var personView = new KeyOnlyRelationView<>(person);
165 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
166
167 var p1 = new Variable("p1");
168 var p2 = new Variable("p2");
169 var predicate = DNF.builder("RelationConstraint")
170 .parameters(p1) 126 .parameters(p1)
171 .clause( 127 .clause(
172 new RelationViewAtom(personView, p1), 128 personView.call(p1),
173 new RelationViewAtom(personView, p2), 129 personView.call(p2),
174 new RelationViewAtom(friendMustView, p1, p2) 130 friendMustView.call(p1, p2)
175 ) 131 )
176 .build(); 132 .build();
177 133
178 var store = ModelStore.builder() 134 var store = ModelStore.builder()
179 .symbols(person, friend) 135 .symbols(person, friend)
180 .with(ViatraModelQuery.ADAPTER) 136 .with(ViatraModelQuery.ADAPTER)
137 .defaultHint(hint)
181 .queries(predicate) 138 .queries(predicate)
182 .build(); 139 .build();
183 140
@@ -194,16 +151,19 @@ class QueryTest {
194 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE); 151 friendInterpretation.put(Tuple.of(0, 1), TruthValue.TRUE);
195 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE); 152 friendInterpretation.put(Tuple.of(1, 0), TruthValue.TRUE);
196 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 153 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
197 154 friendInterpretation.put(Tuple.of(3, 2), TruthValue.TRUE);
198 assertEquals(0, predicateResultSet.countResults());
199 155
200 queryEngine.flushChanges(); 156 queryEngine.flushChanges();
201 assertEquals(2, predicateResultSet.countResults()); 157 assertResults(Map.of(
202 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0), Tuple.of(1))); 158 Tuple.of(0), true,
159 Tuple.of(1), true,
160 Tuple.of(2), false,
161 Tuple.of(3), false
162 ), predicateResultSet);
203 } 163 }
204 164
205 @Test 165 @QueryEngineTest
206 void orTest() { 166 void orTest(QueryEvaluationHint hint) {
207 var person = new Symbol<>("Person", 1, Boolean.class, false); 167 var person = new Symbol<>("Person", 1, Boolean.class, false);
208 var animal = new Symbol<>("Animal", 1, Boolean.class, false); 168 var animal = new Symbol<>("Animal", 1, Boolean.class, false);
209 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 169 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
@@ -211,25 +171,26 @@ class QueryTest {
211 var animalView = new KeyOnlyRelationView<>(animal); 171 var animalView = new KeyOnlyRelationView<>(animal);
212 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 172 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
213 173
214 var p1 = new Variable("p1"); 174 var p1 = Variable.of("p1");
215 var p2 = new Variable("p2"); 175 var p2 = Variable.of("p2");
216 var predicate = DNF.builder("Or") 176 var predicate = Query.builder("Or")
217 .parameters(p1, p2) 177 .parameters(p1, p2)
218 .clause( 178 .clause(
219 new RelationViewAtom(personView, p1), 179 personView.call(p1),
220 new RelationViewAtom(personView, p2), 180 personView.call(p2),
221 new RelationViewAtom(friendMustView, p1, p2) 181 friendMustView.call(p1, p2)
222 ) 182 )
223 .clause( 183 .clause(
224 new RelationViewAtom(animalView, p1), 184 animalView.call(p1),
225 new RelationViewAtom(animalView, p2), 185 animalView.call(p2),
226 new RelationViewAtom(friendMustView, p1, p2) 186 friendMustView.call(p1, p2)
227 ) 187 )
228 .build(); 188 .build();
229 189
230 var store = ModelStore.builder() 190 var store = ModelStore.builder()
231 .symbols(person, animal, friend) 191 .symbols(person, animal, friend)
232 .with(ViatraModelQuery.ADAPTER) 192 .with(ViatraModelQuery.ADAPTER)
193 .defaultHint(hint)
233 .queries(predicate) 194 .queries(predicate)
234 .build(); 195 .build();
235 196
@@ -252,29 +213,35 @@ class QueryTest {
252 friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE); 213 friendInterpretation.put(Tuple.of(3, 0), TruthValue.TRUE);
253 214
254 queryEngine.flushChanges(); 215 queryEngine.flushChanges();
255 assertEquals(2, predicateResultSet.countResults()); 216 assertResults(Map.of(
256 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1), Tuple.of(2, 3))); 217 Tuple.of(0, 1), true,
218 Tuple.of(0, 2), false,
219 Tuple.of(2, 3), true,
220 Tuple.of(3, 0), false,
221 Tuple.of(3, 2), false
222 ), predicateResultSet);
257 } 223 }
258 224
259 @Test 225 @QueryEngineTest
260 void equalityTest() { 226 void equalityTest(QueryEvaluationHint hint) {
261 var person = new Symbol<>("Person", 1, Boolean.class, false); 227 var person = new Symbol<>("Person", 1, Boolean.class, false);
262 var personView = new KeyOnlyRelationView<>(person); 228 var personView = new KeyOnlyRelationView<>(person);
263 229
264 var p1 = new Variable("p1"); 230 var p1 = Variable.of("p1");
265 var p2 = new Variable("p2"); 231 var p2 = Variable.of("p2");
266 var predicate = DNF.builder("Equality") 232 var predicate = Query.builder("Equality")
267 .parameters(p1, p2) 233 .parameters(p1, p2)
268 .clause( 234 .clause(
269 new RelationViewAtom(personView, p1), 235 personView.call(p1),
270 new RelationViewAtom(personView, p2), 236 personView.call(p2),
271 new EquivalenceAtom(p1, p2) 237 p1.isEquivalent(p2)
272 ) 238 )
273 .build(); 239 .build();
274 240
275 var store = ModelStore.builder() 241 var store = ModelStore.builder()
276 .symbols(person) 242 .symbols(person)
277 .with(ViatraModelQuery.ADAPTER) 243 .with(ViatraModelQuery.ADAPTER)
244 .defaultHint(hint)
278 .queries(predicate) 245 .queries(predicate)
279 .build(); 246 .build();
280 247
@@ -288,34 +255,40 @@ class QueryTest {
288 personInterpretation.put(Tuple.of(2), true); 255 personInterpretation.put(Tuple.of(2), true);
289 256
290 queryEngine.flushChanges(); 257 queryEngine.flushChanges();
291 assertEquals(3, predicateResultSet.countResults()); 258 assertResults(Map.of(
292 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 0), Tuple.of(1, 1), Tuple.of(2, 2))); 259 Tuple.of(0, 0), true,
260 Tuple.of(1, 1), true,
261 Tuple.of(2, 2), true,
262 Tuple.of(0, 1), false,
263 Tuple.of(3, 3), false
264 ), predicateResultSet);
293 } 265 }
294 266
295 @Test 267 @QueryEngineTest
296 void inequalityTest() { 268 void inequalityTest(QueryEvaluationHint hint) {
297 var person = new Symbol<>("Person", 1, Boolean.class, false); 269 var person = new Symbol<>("Person", 1, Boolean.class, false);
298 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 270 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
299 var personView = new KeyOnlyRelationView<>(person); 271 var personView = new KeyOnlyRelationView<>(person);
300 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 272 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
301 273
302 var p1 = new Variable("p1"); 274 var p1 = Variable.of("p1");
303 var p2 = new Variable("p2"); 275 var p2 = Variable.of("p2");
304 var p3 = new Variable("p3"); 276 var p3 = Variable.of("p3");
305 var predicate = DNF.builder("Inequality") 277 var predicate = Query.builder("Inequality")
306 .parameters(p1, p2, p3) 278 .parameters(p1, p2, p3)
307 .clause( 279 .clause(
308 new RelationViewAtom(personView, p1), 280 personView.call(p1),
309 new RelationViewAtom(personView, p2), 281 personView.call(p2),
310 new RelationViewAtom(friendMustView, p1, p3), 282 friendMustView.call(p1, p3),
311 new RelationViewAtom(friendMustView, p2, p3), 283 friendMustView.call(p2, p3),
312 new EquivalenceAtom(false, p1, p2) 284 p1.notEquivalent(p2)
313 ) 285 )
314 .build(); 286 .build();
315 287
316 var store = ModelStore.builder() 288 var store = ModelStore.builder()
317 .symbols(person, friend) 289 .symbols(person, friend)
318 .with(ViatraModelQuery.ADAPTER) 290 .with(ViatraModelQuery.ADAPTER)
291 .defaultHint(hint)
319 .queries(predicate) 292 .queries(predicate)
320 .build(); 293 .build();
321 294
@@ -333,42 +306,46 @@ class QueryTest {
333 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 306 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
334 307
335 queryEngine.flushChanges(); 308 queryEngine.flushChanges();
336 assertEquals(2, predicateResultSet.countResults()); 309 assertResults(Map.of(
337 compareMatchSets(predicateResultSet.allResults(), Set.of(Tuple.of(0, 1, 2), Tuple.of(1, 0, 2))); 310 Tuple.of(0, 1, 2), true,
311 Tuple.of(1, 0, 2), true,
312 Tuple.of(0, 0, 2), false
313 ), predicateResultSet);
338 } 314 }
339 315
340 @Test 316 @QueryEngineTest
341 void patternCallTest() { 317 void patternCallTest(QueryEvaluationHint hint) {
342 var person = new Symbol<>("Person", 1, Boolean.class, false); 318 var person = new Symbol<>("Person", 1, Boolean.class, false);
343 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 319 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
344 var personView = new KeyOnlyRelationView<>(person); 320 var personView = new KeyOnlyRelationView<>(person);
345 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 321 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
346 322
347 var p1 = new Variable("p1"); 323 var p1 = Variable.of("p1");
348 var p2 = new Variable("p2"); 324 var p2 = Variable.of("p2");
349 var friendPredicate = DNF.builder("RelationConstraint") 325 var friendPredicate = Dnf.builder("RelationConstraint")
350 .parameters(p1, p2) 326 .parameters(p1, p2)
351 .clause( 327 .clause(
352 new RelationViewAtom(personView, p1), 328 personView.call(p1),
353 new RelationViewAtom(personView, p2), 329 personView.call(p2),
354 new RelationViewAtom(friendMustView, p1, p2) 330 friendMustView.call(p1, p2)
355 ) 331 )
356 .build(); 332 .build();
357 333
358 var p3 = new Variable("p3"); 334 var p3 = Variable.of("p3");
359 var p4 = new Variable("p4"); 335 var p4 = Variable.of("p4");
360 var predicate = DNF.builder("PositivePatternCall") 336 var predicate = Query.builder("PositivePatternCall")
361 .parameters(p3, p4) 337 .parameters(p3, p4)
362 .clause( 338 .clause(
363 new RelationViewAtom(personView, p3), 339 personView.call(p3),
364 new RelationViewAtom(personView, p4), 340 personView.call(p4),
365 new DNFCallAtom(friendPredicate, p3, p4) 341 friendPredicate.call(p3, p4)
366 ) 342 )
367 .build(); 343 .build();
368 344
369 var store = ModelStore.builder() 345 var store = ModelStore.builder()
370 .symbols(person, friend) 346 .symbols(person, friend)
371 .with(ViatraModelQuery.ADAPTER) 347 .with(ViatraModelQuery.ADAPTER)
348 .defaultHint(hint)
372 .queries(predicate) 349 .queries(predicate)
373 .build(); 350 .build();
374 351
@@ -387,30 +364,36 @@ class QueryTest {
387 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 364 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
388 365
389 queryEngine.flushChanges(); 366 queryEngine.flushChanges();
390 assertEquals(3, predicateResultSet.countResults()); 367 assertResults(Map.of(
368 Tuple.of(0, 1), true,
369 Tuple.of(1, 0), true,
370 Tuple.of(1, 2), true,
371 Tuple.of(2, 1), false
372 ), predicateResultSet);
391 } 373 }
392 374
393 @Test 375 @QueryEngineTest
394 void negativeRelationViewTest() { 376 void negativeRelationViewTest(QueryEvaluationHint hint) {
395 var person = new Symbol<>("Person", 1, Boolean.class, false); 377 var person = new Symbol<>("Person", 1, Boolean.class, false);
396 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 378 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
397 var personView = new KeyOnlyRelationView<>(person); 379 var personView = new KeyOnlyRelationView<>(person);
398 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 380 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
399 381
400 var p1 = new Variable("p1"); 382 var p1 = Variable.of("p1");
401 var p2 = new Variable("p2"); 383 var p2 = Variable.of("p2");
402 var predicate = DNF.builder("NegativePatternCall") 384 var predicate = Query.builder("NegativePatternCall")
403 .parameters(p1, p2) 385 .parameters(p1, p2)
404 .clause( 386 .clause(
405 new RelationViewAtom(personView, p1), 387 personView.call(p1),
406 new RelationViewAtom(personView, p2), 388 personView.call(p2),
407 new RelationViewAtom(false, friendMustView, p1, p2) 389 not(friendMustView.call(p1, p2))
408 ) 390 )
409 .build(); 391 .build();
410 392
411 var store = ModelStore.builder() 393 var store = ModelStore.builder()
412 .symbols(person, friend) 394 .symbols(person, friend)
413 .with(ViatraModelQuery.ADAPTER) 395 .with(ViatraModelQuery.ADAPTER)
396 .defaultHint(hint)
414 .queries(predicate) 397 .queries(predicate)
415 .build(); 398 .build();
416 399
@@ -429,41 +412,53 @@ class QueryTest {
429 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 412 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
430 413
431 queryEngine.flushChanges(); 414 queryEngine.flushChanges();
432 assertEquals(6, predicateResultSet.countResults()); 415 assertResults(Map.of(
416 Tuple.of(0, 0), true,
417 Tuple.of(0, 2), true,
418 Tuple.of(1, 1), true,
419 Tuple.of(2, 0), true,
420 Tuple.of(2, 1), true,
421 Tuple.of(2, 2), true,
422 Tuple.of(0, 1), false,
423 Tuple.of(1, 0), false,
424 Tuple.of(1, 2), false,
425 Tuple.of(0, 3), false
426 ), predicateResultSet);
433 } 427 }
434 428
435 @Test 429 @QueryEngineTest
436 void negativePatternCallTest() { 430 void negativePatternCallTest(QueryEvaluationHint hint) {
437 var person = new Symbol<>("Person", 1, Boolean.class, false); 431 var person = new Symbol<>("Person", 1, Boolean.class, false);
438 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 432 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
439 var personView = new KeyOnlyRelationView<>(person); 433 var personView = new KeyOnlyRelationView<>(person);
440 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 434 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
441 435
442 var p1 = new Variable("p1"); 436 var p1 = Variable.of("p1");
443 var p2 = new Variable("p2"); 437 var p2 = Variable.of("p2");
444 var friendPredicate = DNF.builder("RelationConstraint") 438 var friendPredicate = Dnf.builder("RelationConstraint")
445 .parameters(p1, p2) 439 .parameters(p1, p2)
446 .clause( 440 .clause(
447 new RelationViewAtom(personView, p1), 441 personView.call(p1),
448 new RelationViewAtom(personView, p2), 442 personView.call(p2),
449 new RelationViewAtom(friendMustView, p1, p2) 443 friendMustView.call(p1, p2)
450 ) 444 )
451 .build(); 445 .build();
452 446
453 var p3 = new Variable("p3"); 447 var p3 = Variable.of("p3");
454 var p4 = new Variable("p4"); 448 var p4 = Variable.of("p4");
455 var predicate = DNF.builder("NegativePatternCall") 449 var predicate = Query.builder("NegativePatternCall")
456 .parameters(p3, p4) 450 .parameters(p3, p4)
457 .clause( 451 .clause(
458 new RelationViewAtom(personView, p3), 452 personView.call(p3),
459 new RelationViewAtom(personView, p4), 453 personView.call(p4),
460 new DNFCallAtom(false, friendPredicate, p3, p4) 454 not(friendPredicate.call(p3, p4))
461 ) 455 )
462 .build(); 456 .build();
463 457
464 var store = ModelStore.builder() 458 var store = ModelStore.builder()
465 .symbols(person, friend) 459 .symbols(person, friend)
466 .with(ViatraModelQuery.ADAPTER) 460 .with(ViatraModelQuery.ADAPTER)
461 .defaultHint(hint)
467 .queries(predicate) 462 .queries(predicate)
468 .build(); 463 .build();
469 464
@@ -482,30 +477,42 @@ class QueryTest {
482 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 477 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
483 478
484 queryEngine.flushChanges(); 479 queryEngine.flushChanges();
485 assertEquals(6, predicateResultSet.countResults()); 480 assertResults(Map.of(
481 Tuple.of(0, 0), true,
482 Tuple.of(0, 2), true,
483 Tuple.of(1, 1), true,
484 Tuple.of(2, 0), true,
485 Tuple.of(2, 1), true,
486 Tuple.of(2, 2), true,
487 Tuple.of(0, 1), false,
488 Tuple.of(1, 0), false,
489 Tuple.of(1, 2), false,
490 Tuple.of(0, 3), false
491 ), predicateResultSet);
486 } 492 }
487 493
488 @Test 494 @QueryEngineTest
489 void negativeRelationViewWithQuantificationTest() { 495 void negativeRelationViewWithQuantificationTest(QueryEvaluationHint hint) {
490 var person = new Symbol<>("Person", 1, Boolean.class, false); 496 var person = new Symbol<>("Person", 1, Boolean.class, false);
491 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 497 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
492 var personView = new KeyOnlyRelationView<>(person); 498 var personView = new KeyOnlyRelationView<>(person);
493 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 499 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
494 500
495 var p1 = new Variable("p1"); 501 var p1 = Variable.of("p1");
496 var p2 = new Variable("p2"); 502 var p2 = Variable.of("p2");
497 503
498 var predicate = DNF.builder("Count") 504 var predicate = Query.builder("Count")
499 .parameters(p1) 505 .parameters(p1)
500 .clause( 506 .clause(
501 new RelationViewAtom(personView, p1), 507 personView.call(p1),
502 new RelationViewAtom(false, friendMustView, p1, p2) 508 not(friendMustView.call(p1, p2))
503 ) 509 )
504 .build(); 510 .build();
505 511
506 var store = ModelStore.builder() 512 var store = ModelStore.builder()
507 .symbols(person, friend) 513 .symbols(person, friend)
508 .with(ViatraModelQuery.ADAPTER) 514 .with(ViatraModelQuery.ADAPTER)
515 .defaultHint(hint)
509 .queries(predicate) 516 .queries(predicate)
510 .build(); 517 .build();
511 518
@@ -523,39 +530,45 @@ class QueryTest {
523 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); 530 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
524 531
525 queryEngine.flushChanges(); 532 queryEngine.flushChanges();
526 assertEquals(2, predicateResultSet.countResults()); 533 assertResults(Map.of(
534 Tuple.of(0), false,
535 Tuple.of(1), true,
536 Tuple.of(2), true,
537 Tuple.of(3), false
538 ), predicateResultSet);
527 } 539 }
528 540
529 @Test 541 @QueryEngineTest
530 void negativeWithQuantificationTest() { 542 void negativeWithQuantificationTest(QueryEvaluationHint hint) {
531 var person = new Symbol<>("Person", 1, Boolean.class, false); 543 var person = new Symbol<>("Person", 1, Boolean.class, false);
532 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 544 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
533 var personView = new KeyOnlyRelationView<>(person); 545 var personView = new KeyOnlyRelationView<>(person);
534 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 546 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
535 547
536 var p1 = new Variable("p1"); 548 var p1 = Variable.of("p1");
537 var p2 = new Variable("p2"); 549 var p2 = Variable.of("p2");
538 550
539 var called = DNF.builder("Called") 551 var called = Dnf.builder("Called")
540 .parameters(p1, p2) 552 .parameters(p1, p2)
541 .clause( 553 .clause(
542 new RelationViewAtom(personView, p1), 554 personView.call(p1),
543 new RelationViewAtom(personView, p2), 555 personView.call(p2),
544 new RelationViewAtom(friendMustView, p1, p2) 556 friendMustView.call(p1, p2)
545 ) 557 )
546 .build(); 558 .build();
547 559
548 var predicate = DNF.builder("Count") 560 var predicate = Query.builder("Count")
549 .parameters(p1) 561 .parameters(p1)
550 .clause( 562 .clause(
551 new RelationViewAtom(personView, p1), 563 personView.call(p1),
552 new DNFCallAtom(false, called, p1, p2) 564 not(called.call(p1, p2))
553 ) 565 )
554 .build(); 566 .build();
555 567
556 var store = ModelStore.builder() 568 var store = ModelStore.builder()
557 .symbols(person, friend) 569 .symbols(person, friend)
558 .with(ViatraModelQuery.ADAPTER) 570 .with(ViatraModelQuery.ADAPTER)
571 .defaultHint(hint)
559 .queries(predicate) 572 .queries(predicate)
560 .build(); 573 .build();
561 574
@@ -573,30 +586,36 @@ class QueryTest {
573 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE); 586 friendInterpretation.put(Tuple.of(0, 2), TruthValue.TRUE);
574 587
575 queryEngine.flushChanges(); 588 queryEngine.flushChanges();
576 assertEquals(2, predicateResultSet.countResults()); 589 assertResults(Map.of(
590 Tuple.of(0), false,
591 Tuple.of(1), true,
592 Tuple.of(2), true,
593 Tuple.of(3), false
594 ), predicateResultSet);
577 } 595 }
578 596
579 @Test 597 @QueryEngineTest
580 void transitiveRelationViewTest() { 598 void transitiveRelationViewTest(QueryEvaluationHint hint) {
581 var person = new Symbol<>("Person", 1, Boolean.class, false); 599 var person = new Symbol<>("Person", 1, Boolean.class, false);
582 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 600 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
583 var personView = new KeyOnlyRelationView<>(person); 601 var personView = new KeyOnlyRelationView<>(person);
584 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 602 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
585 603
586 var p1 = new Variable("p1"); 604 var p1 = Variable.of("p1");
587 var p2 = new Variable("p2"); 605 var p2 = Variable.of("p2");
588 var predicate = DNF.builder("TransitivePatternCall") 606 var predicate = Query.builder("TransitivePatternCall")
589 .parameters(p1, p2) 607 .parameters(p1, p2)
590 .clause( 608 .clause(
591 new RelationViewAtom(personView, p1), 609 personView.call(p1),
592 new RelationViewAtom(personView, p2), 610 personView.call(p2),
593 new RelationViewAtom(CallPolarity.TRANSITIVE, friendMustView, p1, p2) 611 friendMustView.callTransitive(p1, p2)
594 ) 612 )
595 .build(); 613 .build();
596 614
597 var store = ModelStore.builder() 615 var store = ModelStore.builder()
598 .symbols(person, friend) 616 .symbols(person, friend)
599 .with(ViatraModelQuery.ADAPTER) 617 .with(ViatraModelQuery.ADAPTER)
618 .defaultHint(hint)
600 .queries(predicate) 619 .queries(predicate)
601 .build(); 620 .build();
602 621
@@ -614,41 +633,53 @@ class QueryTest {
614 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 633 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
615 634
616 queryEngine.flushChanges(); 635 queryEngine.flushChanges();
617 assertEquals(3, predicateResultSet.countResults()); 636 assertResults(Map.of(
637 Tuple.of(0, 0), false,
638 Tuple.of(0, 1), true,
639 Tuple.of(0, 2), true,
640 Tuple.of(1, 0), false,
641 Tuple.of(1, 1), false,
642 Tuple.of(1, 2), true,
643 Tuple.of(2, 0), false,
644 Tuple.of(2, 1), false,
645 Tuple.of(2, 2), false,
646 Tuple.of(2, 3), false
647 ), predicateResultSet);
618 } 648 }
619 649
620 @Test 650 @QueryEngineTest
621 void transitivePatternCallTest() { 651 void transitivePatternCallTest(QueryEvaluationHint hint) {
622 var person = new Symbol<>("Person", 1, Boolean.class, false); 652 var person = new Symbol<>("Person", 1, Boolean.class, false);
623 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE); 653 var friend = new Symbol<>("friend", 2, TruthValue.class, TruthValue.FALSE);
624 var personView = new KeyOnlyRelationView<>(person); 654 var personView = new KeyOnlyRelationView<>(person);
625 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must); 655 var friendMustView = new FilteredRelationView<>(friend, "must", TruthValue::must);
626 656
627 var p1 = new Variable("p1"); 657 var p1 = Variable.of("p1");
628 var p2 = new Variable("p2"); 658 var p2 = Variable.of("p2");
629 var friendPredicate = DNF.builder("RelationConstraint") 659 var friendPredicate = Dnf.builder("RelationConstraint")
630 .parameters(p1, p2) 660 .parameters(p1, p2)
631 .clause( 661 .clause(
632 new RelationViewAtom(personView, p1), 662 personView.call(p1),
633 new RelationViewAtom(personView, p2), 663 personView.call(p2),
634 new RelationViewAtom(friendMustView, p1, p2) 664 friendMustView.call(p1, p2)
635 ) 665 )
636 .build(); 666 .build();
637 667
638 var p3 = new Variable("p3"); 668 var p3 = Variable.of("p3");
639 var p4 = new Variable("p4"); 669 var p4 = Variable.of("p4");
640 var predicate = DNF.builder("TransitivePatternCall") 670 var predicate = Query.builder("TransitivePatternCall")
641 .parameters(p3, p4) 671 .parameters(p3, p4)
642 .clause( 672 .clause(
643 new RelationViewAtom(personView, p3), 673 personView.call(p3),
644 new RelationViewAtom(personView, p4), 674 personView.call(p4),
645 new DNFCallAtom(CallPolarity.TRANSITIVE, friendPredicate, p3, p4) 675 friendPredicate.callTransitive(p3, p4)
646 ) 676 )
647 .build(); 677 .build();
648 678
649 var store = ModelStore.builder() 679 var store = ModelStore.builder()
650 .symbols(person, friend) 680 .symbols(person, friend)
651 .with(ViatraModelQuery.ADAPTER) 681 .with(ViatraModelQuery.ADAPTER)
682 .defaultHint(hint)
652 .queries(predicate) 683 .queries(predicate)
653 .build(); 684 .build();
654 685
@@ -666,16 +697,101 @@ class QueryTest {
666 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE); 697 friendInterpretation.put(Tuple.of(1, 2), TruthValue.TRUE);
667 698
668 queryEngine.flushChanges(); 699 queryEngine.flushChanges();
669 assertEquals(3, predicateResultSet.countResults()); 700 assertResults(Map.of(
701 Tuple.of(0, 0), false,
702 Tuple.of(0, 1), true,
703 Tuple.of(0, 2), true,
704 Tuple.of(1, 0), false,
705 Tuple.of(1, 1), false,
706 Tuple.of(1, 2), true,
707 Tuple.of(2, 0), false,
708 Tuple.of(2, 1), false,
709 Tuple.of(2, 2), false,
710 Tuple.of(2, 3), false
711 ), predicateResultSet);
712 }
713
714 @QueryEngineTest
715 void assumeTest(QueryEvaluationHint hint) {
716 var person = new Symbol<>("Person", 1, Boolean.class, false);
717 var age = new Symbol<>("age", 1, Integer.class, null);
718 var personView = new KeyOnlyRelationView<>(person);
719 var ageView = new FunctionalRelationView<>(age);
720
721 var p1 = Variable.of("p1");
722 var x = Variable.of("x", Integer.class);
723 var query = Query.builder("Constraint")
724 .parameter(p1)
725 .clause(
726 personView.call(p1),
727 ageView.call(p1, x),
728 assume(greaterEq(x, constant(18)))
729 )
730 .build();
731
732 var store = ModelStore.builder()
733 .symbols(person, age)
734 .with(ViatraModelQuery.ADAPTER)
735 .defaultHint(hint)
736 .queries(query)
737 .build();
738
739 var model = store.createEmptyModel();
740 var personInterpretation = model.getInterpretation(person);
741 var ageInterpretation = model.getInterpretation(age);
742 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
743 var queryResultSet = queryEngine.getResultSet(query);
744
745 personInterpretation.put(Tuple.of(0), true);
746 personInterpretation.put(Tuple.of(1), true);
747
748 ageInterpretation.put(Tuple.of(0), 12);
749 ageInterpretation.put(Tuple.of(1), 24);
750
751 queryEngine.flushChanges();
752 assertResults(Map.of(
753 Tuple.of(0), false,
754 Tuple.of(1), true,
755 Tuple.of(2), false
756 ), queryResultSet);
757 }
758
759 @Test
760 void alwaysFalseTest() {
761 var person = new Symbol<>("Person", 1, Boolean.class, false);
762
763 var p1 = Variable.of("p1");
764 var predicate = Query.builder("AlwaysFalse").parameters(p1).build();
765
766 var store = ModelStore.builder()
767 .symbols(person)
768 .with(ViatraModelQuery.ADAPTER)
769 .queries(predicate)
770 .build();
771
772 var model = store.createEmptyModel();
773 var personInterpretation = model.getInterpretation(person);
774 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
775 var predicateResultSet = queryEngine.getResultSet(predicate);
776
777 personInterpretation.put(Tuple.of(0), true);
778 personInterpretation.put(Tuple.of(1), true);
779 personInterpretation.put(Tuple.of(2), true);
780
781 queryEngine.flushChanges();
782 assertResults(Map.of(), predicateResultSet);
670 } 783 }
671 784
672 static void compareMatchSets(Stream<TupleLike> matchSet, Set<Tuple> expected) { 785 @Test
673 Set<Tuple> translatedMatchSet = new HashSet<>(); 786 void alwaysTrueTest() {
674 var iterator = matchSet.iterator(); 787 var person = new Symbol<>("Person", 1, Boolean.class, false);
675 while (iterator.hasNext()) { 788
676 var element = iterator.next(); 789 var p1 = Variable.of("p1");
677 translatedMatchSet.add(element.toTuple()); 790 var predicate = Query.builder("AlwaysTrue").parameters(p1).clause().build();
678 } 791
679 assertEquals(expected, translatedMatchSet); 792 var storeBuilder = ModelStore.builder().symbols(person);
793 var queryBuilder = storeBuilder.with(ViatraModelQuery.ADAPTER);
794
795 assertThrows(IllegalArgumentException.class, () -> queryBuilder.queries(predicate));
680 } 796 }
681} 797}
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 98995339..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,28 +1,162 @@
1package tools.refinery.store.query.viatra; 1package tools.refinery.store.query.viatra;
2 2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import org.junit.jupiter.api.Disabled;
3import org.junit.jupiter.api.Test; 5import org.junit.jupiter.api.Test;
4import tools.refinery.store.model.ModelStore; 6import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.DNF;
6import tools.refinery.store.query.ModelQuery; 7import tools.refinery.store.query.ModelQuery;
7import tools.refinery.store.query.Variable; 8import tools.refinery.store.query.dnf.Query;
8import tools.refinery.store.query.atom.RelationViewAtom; 9import tools.refinery.store.query.term.Variable;
10import tools.refinery.store.query.view.FilteredRelationView;
11import tools.refinery.store.query.view.FunctionalRelationView;
9import tools.refinery.store.query.view.KeyOnlyRelationView; 12import tools.refinery.store.query.view.KeyOnlyRelationView;
10import tools.refinery.store.representation.Symbol; 13import tools.refinery.store.representation.Symbol;
11import tools.refinery.store.tuple.Tuple; 14import tools.refinery.store.tuple.Tuple;
12 15
13import static org.junit.jupiter.api.Assertions.*; 16import java.util.Map;
17import java.util.Optional;
18
19import static org.junit.jupiter.api.Assertions.assertFalse;
20import static org.junit.jupiter.api.Assertions.assertTrue;
21import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertNullableResults;
22import static tools.refinery.store.query.viatra.tests.QueryAssertions.assertResults;
14 23
15class QueryTransactionTest { 24class QueryTransactionTest {
16 @Test 25 @Test
17 void flushTest() { 26 void flushTest() {
18 var person = new Symbol<>("Person", 1, Boolean.class, false); 27 var person = new Symbol<>("Person", 1, Boolean.class, false);
28 var personView = new KeyOnlyRelationView<>(person);
29
30 var p1 = Variable.of("p1");
31 var predicate = Query.builder("TypeConstraint")
32 .parameters(p1)
33 .clause(personView.call(p1))
34 .build();
35
36 var store = ModelStore.builder()
37 .symbols(person)
38 .with(ViatraModelQuery.ADAPTER)
39 .queries(predicate)
40 .build();
41
42 var model = store.createEmptyModel();
43 var personInterpretation = model.getInterpretation(person);
44 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
45 var predicateResultSet = queryEngine.getResultSet(predicate);
46
47 assertResults(Map.of(
48 Tuple.of(0), false,
49 Tuple.of(1), false,
50 Tuple.of(2), false,
51 Tuple.of(3), false
52 ), predicateResultSet);
53 assertFalse(queryEngine.hasPendingChanges());
54
55 personInterpretation.put(Tuple.of(0), true);
56 personInterpretation.put(Tuple.of(1), true);
57
58 assertResults(Map.of(
59 Tuple.of(0), false,
60 Tuple.of(1), false,
61 Tuple.of(2), false,
62 Tuple.of(3), false
63 ), predicateResultSet);
64 assertTrue(queryEngine.hasPendingChanges());
65
66 queryEngine.flushChanges();
67 assertResults(Map.of(
68 Tuple.of(0), true,
69 Tuple.of(1), true,
70 Tuple.of(2), false,
71 Tuple.of(3), false
72 ), predicateResultSet);
73 assertFalse(queryEngine.hasPendingChanges());
74
75 personInterpretation.put(Tuple.of(1), false);
76 personInterpretation.put(Tuple.of(2), true);
77
78 assertResults(Map.of(
79 Tuple.of(0), true,
80 Tuple.of(1), true,
81 Tuple.of(2), false,
82 Tuple.of(3), false
83 ), predicateResultSet);
84 assertTrue(queryEngine.hasPendingChanges());
85
86 queryEngine.flushChanges();
87 assertResults(Map.of(
88 Tuple.of(0), true,
89 Tuple.of(1), false,
90 Tuple.of(2), true,
91 Tuple.of(3), false
92 ), predicateResultSet);
93 assertFalse(queryEngine.hasPendingChanges());
94 }
95
96 @Test
97 void localSearchTest() {
98 var person = new Symbol<>("Person", 1, Boolean.class, false);
99 var personView = new KeyOnlyRelationView<>(person);
100
101 var p1 = Variable.of("p1");
102 var predicate = Query.builder("TypeConstraint")
103 .parameters(p1)
104 .clause(personView.call(p1))
105 .build();
106
107 var store = ModelStore.builder()
108 .symbols(person)
109 .with(ViatraModelQuery.ADAPTER)
110 .defaultHint(new QueryEvaluationHint(null, QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH))
111 .queries(predicate)
112 .build();
113
114 var model = store.createEmptyModel();
115 var personInterpretation = model.getInterpretation(person);
116 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
117 var predicateResultSet = queryEngine.getResultSet(predicate);
118
119 assertResults(Map.of(
120 Tuple.of(0), false,
121 Tuple.of(1), false,
122 Tuple.of(2), false,
123 Tuple.of(3), false
124 ), predicateResultSet);
125 assertFalse(queryEngine.hasPendingChanges());
126
127 personInterpretation.put(Tuple.of(0), true);
128 personInterpretation.put(Tuple.of(1), true);
129
130 assertResults(Map.of(
131 Tuple.of(0), true,
132 Tuple.of(1), true,
133 Tuple.of(2), false,
134 Tuple.of(3), false
135 ), predicateResultSet);
136 assertFalse(queryEngine.hasPendingChanges());
137
138 personInterpretation.put(Tuple.of(1), false);
139 personInterpretation.put(Tuple.of(2), true);
140
141 assertResults(Map.of(
142 Tuple.of(0), true,
143 Tuple.of(1), false,
144 Tuple.of(2), true,
145 Tuple.of(3), false
146 ), predicateResultSet);
147 assertFalse(queryEngine.hasPendingChanges());
148 }
149
150 @Test
151 void unrelatedChangesTest() {
152 var person = new Symbol<>("Person", 1, Boolean.class, false);
19 var asset = new Symbol<>("Asset", 1, Boolean.class, false); 153 var asset = new Symbol<>("Asset", 1, Boolean.class, false);
20 var personView = new KeyOnlyRelationView<>(person); 154 var personView = new KeyOnlyRelationView<>(person);
21 155
22 var p1 = new Variable("p1"); 156 var p1 = Variable.of("p1");
23 var predicate = DNF.builder("TypeConstraint") 157 var predicate = Query.builder("TypeConstraint")
24 .parameters(p1) 158 .parameters(p1)
25 .clause(new RelationViewAtom(personView, p1)) 159 .clause(personView.call(p1))
26 .build(); 160 .build();
27 161
28 var store = ModelStore.builder() 162 var store = ModelStore.builder()
@@ -37,7 +171,6 @@ class QueryTransactionTest {
37 var queryEngine = model.getAdapter(ModelQuery.ADAPTER); 171 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
38 var predicateResultSet = queryEngine.getResultSet(predicate); 172 var predicateResultSet = queryEngine.getResultSet(predicate);
39 173
40 assertEquals(0, predicateResultSet.countResults());
41 assertFalse(queryEngine.hasPendingChanges()); 174 assertFalse(queryEngine.hasPendingChanges());
42 175
43 personInterpretation.put(Tuple.of(0), true); 176 personInterpretation.put(Tuple.of(0), true);
@@ -46,19 +179,245 @@ class QueryTransactionTest {
46 assetInterpretation.put(Tuple.of(1), true); 179 assetInterpretation.put(Tuple.of(1), true);
47 assetInterpretation.put(Tuple.of(2), true); 180 assetInterpretation.put(Tuple.of(2), true);
48 181
49 assertEquals(0, predicateResultSet.countResults()); 182 assertResults(Map.of(
183 Tuple.of(0), false,
184 Tuple.of(1), false,
185 Tuple.of(2), false,
186 Tuple.of(3), false,
187 Tuple.of(4), false
188 ), predicateResultSet);
50 assertTrue(queryEngine.hasPendingChanges()); 189 assertTrue(queryEngine.hasPendingChanges());
51 190
52 queryEngine.flushChanges(); 191 queryEngine.flushChanges();
53 assertEquals(2, predicateResultSet.countResults()); 192 assertResults(Map.of(
193 Tuple.of(0), true,
194 Tuple.of(1), true,
195 Tuple.of(2), false,
196 Tuple.of(3), false,
197 Tuple.of(4), false
198 ), predicateResultSet);
54 assertFalse(queryEngine.hasPendingChanges()); 199 assertFalse(queryEngine.hasPendingChanges());
55 200
56 personInterpretation.put(Tuple.of(4), true); 201 assetInterpretation.put(Tuple.of(3), true);
57 assertEquals(2, predicateResultSet.countResults()); 202 assertFalse(queryEngine.hasPendingChanges());
58 assertTrue(queryEngine.hasPendingChanges()); 203
204 assertResults(Map.of(
205 Tuple.of(0), true,
206 Tuple.of(1), true,
207 Tuple.of(2), false,
208 Tuple.of(3), false,
209 Tuple.of(4), false
210 ), predicateResultSet);
211
212 queryEngine.flushChanges();
213 assertResults(Map.of(
214 Tuple.of(0), true,
215 Tuple.of(1), true,
216 Tuple.of(2), false,
217 Tuple.of(3), false,
218 Tuple.of(4), false
219 ), predicateResultSet);
220 assertFalse(queryEngine.hasPendingChanges());
221 }
222
223 @Test
224 void tupleChangingChangeTest() {
225 var person = new Symbol<>("Person", 1, Boolean.class, false);
226 var age = new Symbol<>("age", 1, Integer.class, null);
227 var personView = new KeyOnlyRelationView<>(person);
228 var ageView = new FunctionalRelationView<>(age);
229
230 var p1 = Variable.of("p1");
231 var x = Variable.of("x", Integer.class);
232 var query = Query.builder()
233 .parameter(p1)
234 .output(x)
235 .clause(
236 personView.call(p1),
237 ageView.call(p1, x)
238 )
239 .build();
240
241 var store = ModelStore.builder()
242 .symbols(person, age)
243 .with(ViatraModelQuery.ADAPTER)
244 .query(query)
245 .build();
246
247 var model = store.createEmptyModel();
248 var personInterpretation = model.getInterpretation(person);
249 var ageInterpretation = model.getInterpretation(age);
250 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
251 var queryResultSet = queryEngine.getResultSet(query);
252
253 personInterpretation.put(Tuple.of(0), true);
254
255 ageInterpretation.put(Tuple.of(0), 24);
256
257 queryEngine.flushChanges();
258 assertResults(Map.of(Tuple.of(0), 24), queryResultSet);
259
260 ageInterpretation.put(Tuple.of(0), 25);
261
262 queryEngine.flushChanges();
263 assertResults(Map.of(Tuple.of(0), 25), queryResultSet);
264
265 ageInterpretation.put(Tuple.of(0), null);
59 266
60 queryEngine.flushChanges(); 267 queryEngine.flushChanges();
61 assertEquals(3, predicateResultSet.countResults()); 268 assertNullableResults(Map.of(Tuple.of(0), Optional.empty()), queryResultSet);
269 }
270
271 @Test
272 void tuplePreservingUnchangedTest() {
273 var person = new Symbol<>("Person", 1, Boolean.class, false);
274 var age = new Symbol<>("age", 1, Integer.class, null);
275 var personView = new KeyOnlyRelationView<>(person);
276 var adultView = new FilteredRelationView<>(age, "adult", n -> n != null && n >= 18);
277
278 var p1 = Variable.of("p1");
279 var x = Variable.of("x", Integer.class);
280 var query = Query.builder()
281 .parameter(p1)
282 .clause(
283 personView.call(p1),
284 adultView.call(p1)
285 )
286 .build();
287
288 var store = ModelStore.builder()
289 .symbols(person, age)
290 .with(ViatraModelQuery.ADAPTER)
291 .query(query)
292 .build();
293
294 var model = store.createEmptyModel();
295 var personInterpretation = model.getInterpretation(person);
296 var ageInterpretation = model.getInterpretation(age);
297 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
298 var queryResultSet = queryEngine.getResultSet(query);
299
300 personInterpretation.put(Tuple.of(0), true);
301
302 ageInterpretation.put(Tuple.of(0), 24);
303
304 queryEngine.flushChanges();
305 assertResults(Map.of(Tuple.of(0), true), queryResultSet);
306
307 ageInterpretation.put(Tuple.of(0), 25);
308
309 queryEngine.flushChanges();
310 assertResults(Map.of(Tuple.of(0), true), queryResultSet);
311
312 ageInterpretation.put(Tuple.of(0), 17);
313
314 queryEngine.flushChanges();
315 assertResults(Map.of(Tuple.of(0), false), queryResultSet);
316 }
317
318 @Disabled("TODO Fix DiffCursor")
319 @Test
320 void commitAfterFlushTest() {
321 var person = new Symbol<>("Person", 1, Boolean.class, false);
322 var personView = new KeyOnlyRelationView<>(person);
323
324 var p1 = Variable.of("p1");
325 var predicate = Query.builder("TypeConstraint")
326 .parameters(p1)
327 .clause(personView.call(p1))
328 .build();
329
330 var store = ModelStore.builder()
331 .symbols(person)
332 .with(ViatraModelQuery.ADAPTER)
333 .queries(predicate)
334 .build();
335
336 var model = store.createEmptyModel();
337 var personInterpretation = model.getInterpretation(person);
338 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
339 var predicateResultSet = queryEngine.getResultSet(predicate);
340
341 personInterpretation.put(Tuple.of(0), true);
342 personInterpretation.put(Tuple.of(1), true);
343
344 queryEngine.flushChanges();
345 assertResults(Map.of(
346 Tuple.of(0), true,
347 Tuple.of(1), true,
348 Tuple.of(2), false,
349 Tuple.of(3), false
350 ), predicateResultSet);
351
352 var state1 = model.commit();
353
354 personInterpretation.put(Tuple.of(1), false);
355 personInterpretation.put(Tuple.of(2), true);
356
357 queryEngine.flushChanges();
358 assertResults(Map.of(
359 Tuple.of(0), true,
360 Tuple.of(1), false,
361 Tuple.of(2), true,
362 Tuple.of(3), false
363 ), predicateResultSet);
364
365 model.restore(state1);
366
367 assertFalse(queryEngine.hasPendingChanges());
368 assertResults(Map.of(
369 Tuple.of(0), true,
370 Tuple.of(1), true,
371 Tuple.of(2), false,
372 Tuple.of(3), false
373 ), predicateResultSet);
374 }
375
376 @Disabled("TODO Fix DiffCursor")
377 @Test
378 void commitWithoutFlushTest() {
379 var person = new Symbol<>("Person", 1, Boolean.class, false);
380 var personView = new KeyOnlyRelationView<>(person);
381
382 var p1 = Variable.of("p1");
383 var predicate = Query.builder("TypeConstraint")
384 .parameters(p1)
385 .clause(personView.call(p1))
386 .build();
387
388 var store = ModelStore.builder()
389 .symbols(person)
390 .with(ViatraModelQuery.ADAPTER)
391 .queries(predicate)
392 .build();
393
394 var model = store.createEmptyModel();
395 var personInterpretation = model.getInterpretation(person);
396 var queryEngine = model.getAdapter(ModelQuery.ADAPTER);
397 var predicateResultSet = queryEngine.getResultSet(predicate);
398
399 personInterpretation.put(Tuple.of(0), true);
400 personInterpretation.put(Tuple.of(1), true);
401
402 assertResults(Map.of(), predicateResultSet);
403 assertTrue(queryEngine.hasPendingChanges());
404
405 var state1 = model.commit();
406
407 personInterpretation.put(Tuple.of(1), false);
408 personInterpretation.put(Tuple.of(2), true);
409
410 assertResults(Map.of(), predicateResultSet);
411 assertTrue(queryEngine.hasPendingChanges());
412
413 model.restore(state1);
414
415 assertResults(Map.of(
416 Tuple.of(0), true,
417 Tuple.of(1), true,
418 Tuple.of(2), false,
419 Tuple.of(3), false
420 ), predicateResultSet);
62 assertFalse(queryEngine.hasPendingChanges()); 421 assertFalse(queryEngine.hasPendingChanges());
63 } 422 }
64} 423}
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 @@
1package tools.refinery.store.query.viatra.tests;
2
3import org.junit.jupiter.api.function.Executable;
4import tools.refinery.store.query.ResultSet;
5import tools.refinery.store.tuple.Tuple;
6
7import java.util.*;
8
9import static org.hamcrest.MatcherAssert.assertThat;
10import static org.hamcrest.Matchers.is;
11import static org.hamcrest.Matchers.nullValue;
12import static org.junit.jupiter.api.Assertions.assertAll;
13
14public final class QueryAssertions {
15 private QueryAssertions() {
16 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
17 }
18
19 public static <T> void assertNullableResults(Map<Tuple, Optional<T>> expected, ResultSet<T> resultSet) {
20 var nullableValuesMap = new LinkedHashMap<Tuple, T>(expected.size());
21 for (var entry : expected.entrySet()) {
22 nullableValuesMap.put(entry.getKey(), entry.getValue().orElse(null));
23 }
24 assertResults(nullableValuesMap, resultSet);
25 }
26
27 public static <T> void assertResults(Map<Tuple, T> expected, ResultSet<T> resultSet) {
28 var defaultValue = resultSet.getQuery().defaultValue();
29 var filteredExpected = new LinkedHashMap<Tuple, T>();
30 var executables = new ArrayList<Executable>();
31 for (var entry : expected.entrySet()) {
32 var key = entry.getKey();
33 var value = entry.getValue();
34 if (!Objects.equals(value, defaultValue)) {
35 filteredExpected.put(key, value);
36 }
37 executables.add(() -> assertThat("value for key " + key,resultSet.get(key), is(value)));
38 }
39 executables.add(() -> assertThat("results size", resultSet.size(), is(filteredExpected.size())));
40
41 var actual = new LinkedHashMap<Tuple, T>();
42 var cursor = resultSet.getAll();
43 while (cursor.move()) {
44 var key = cursor.getKey();
45 var previous = actual.put(key.toTuple(), cursor.getValue());
46 assertThat("duplicate value for key " + key, previous, nullValue());
47 }
48 executables.add(() -> assertThat("results cursor", actual, is(filteredExpected)));
49
50 assertAll(executables);
51 }
52}
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 @@
1package tools.refinery.store.query.viatra.tests;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4
5/**
6 * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names.
7 */
8class QueryBackendHint extends QueryEvaluationHint {
9 public QueryBackendHint(BackendRequirement backendRequirementType) {
10 super(null, backendRequirementType);
11 }
12
13 @Override
14 public String toString() {
15 return switch (getQueryBackendRequirementType()) {
16 case UNSPECIFIED -> "default";
17 case DEFAULT_CACHING -> "incremental";
18 case DEFAULT_SEARCH -> "localSearch";
19 default -> throw new IllegalStateException("Unknown BackendRequirement");
20 };
21 }
22}
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 @@
1package tools.refinery.store.query.viatra.tests;
2
3import org.junit.jupiter.params.ParameterizedTest;
4import org.junit.jupiter.params.provider.ArgumentsSource;
5
6import java.lang.annotation.ElementType;
7import java.lang.annotation.Retention;
8import java.lang.annotation.RetentionPolicy;
9import java.lang.annotation.Target;
10
11@ParameterizedTest(name = "backend = {0}")
12@ArgumentsSource(QueryEvaluationHintSource.class)
13@Target(ElementType.METHOD)
14@Retention(RetentionPolicy.RUNTIME)
15public @interface QueryEngineTest {
16}
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 @@
1package tools.refinery.store.query.viatra.tests;
2
3import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint;
4import org.junit.jupiter.api.extension.ExtensionContext;
5import org.junit.jupiter.params.provider.Arguments;
6import org.junit.jupiter.params.provider.ArgumentsProvider;
7
8import java.util.stream.Stream;
9
10public class QueryEvaluationHintSource implements ArgumentsProvider {
11 @Override
12 public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
13 return Stream.of(
14 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.UNSPECIFIED)),
15 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_CACHING)),
16 Arguments.of(new QueryBackendHint(QueryEvaluationHint.BackendRequirement.DEFAULT_SEARCH))
17 );
18 }
19}
diff --git a/subprojects/store-query/build.gradle b/subprojects/store-query/build.gradle
new file mode 100644
index 00000000..97761936
--- /dev/null
+++ b/subprojects/store-query/build.gradle
@@ -0,0 +1,9 @@
1plugins {
2 id 'refinery-java-library'
3 id 'refinery-java-test-fixtures'
4}
5
6dependencies {
7 api project(':refinery-store')
8 testFixturesApi libs.hamcrest
9}
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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.dnf.AnyQuery;
4
5public sealed interface AnyResultSet permits ResultSet {
6 ModelQueryAdapter getAdapter();
7
8 AnyQuery getQuery();
9
10 int size();
11}
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 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.literal.*;
5import tools.refinery.store.query.term.*;
6
7import java.util.List;
8
9public interface Constraint {
10 String name();
11
12 List<Sort> getSorts();
13
14 default int arity() {
15 return getSorts().size();
16 }
17
18 default boolean invalidIndex(int i) {
19 return i < 0 || i >= arity();
20 }
21
22 default LiteralReduction getReduction() {
23 return LiteralReduction.NOT_REDUCIBLE;
24 }
25
26 default boolean equals(LiteralEqualityHelper helper, Constraint other) {
27 return equals(other);
28 }
29
30 String toReferenceString();
31
32 default CallLiteral call(CallPolarity polarity, List<Variable> arguments) {
33 return new CallLiteral(polarity, this, arguments);
34 }
35
36 default CallLiteral call(CallPolarity polarity, Variable... arguments) {
37 return call(polarity, List.of(arguments));
38 }
39
40 default CallLiteral call(Variable... arguments) {
41 return call(CallPolarity.POSITIVE, arguments);
42 }
43
44 default CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
45 return call(CallPolarity.TRANSITIVE, List.of(left, right));
46 }
47
48 default AssignedValue<Integer> count(List<Variable> arguments) {
49 return targetVariable -> new CountLiteral(targetVariable, this, arguments);
50 }
51
52 default AssignedValue<Integer> count(Variable... arguments) {
53 return count(List.of(arguments));
54 }
55
56 default <R, T> AssignedValue<R> aggregate(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
57 List<Variable> arguments) {
58 return targetVariable -> new AggregationLiteral<>(targetVariable, aggregator, inputVariable, this, arguments);
59 }
60
61 default <R, T> AssignedValue<R> aggregate(DataVariable<T> inputVariable, Aggregator<R, T> aggregator,
62 Variable... arguments) {
63 return aggregate(inputVariable, aggregator, List.of(arguments));
64 }
65}
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
new file mode 100644
index 00000000..9af73bdd
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/EmptyResultSet.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.map.Cursors;
5import tools.refinery.store.query.dnf.Query;
6import tools.refinery.store.tuple.TupleLike;
7
8public record EmptyResultSet<T>(ModelQueryAdapter adapter, Query<T> query) implements ResultSet<T> {
9 @Override
10 public ModelQueryAdapter getAdapter() {
11 return adapter;
12 }
13
14 @Override
15 public Query<T> getQuery() {
16 return query;
17 }
18
19 @Override
20 public T get(TupleLike parameters) {
21 return query.defaultValue();
22 }
23
24
25 @Override
26 public Cursor<TupleLike, T> getAll() {
27 return Cursors.empty();
28 }
29
30 @Override
31 public int size() {
32 return 0;
33 }
34}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
index 6a1aeabb..6a1aeabb 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQuery.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQuery.java
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryAdapter.java
index 7449e39b..2e30fec4 100644
--- a/subprojects/store/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 @@
1package tools.refinery.store.query; 1package tools.refinery.store.query;
2 2
3import tools.refinery.store.adapter.ModelAdapter; 3import tools.refinery.store.adapter.ModelAdapter;
4import tools.refinery.store.query.dnf.AnyQuery;
5import tools.refinery.store.query.dnf.Query;
4 6
5public interface ModelQueryAdapter extends ModelAdapter { 7public interface ModelQueryAdapter extends ModelAdapter {
6 ModelQueryStoreAdapter getStoreAdapter(); 8 ModelQueryStoreAdapter getStoreAdapter();
7 9
8 ResultSet getResultSet(DNF query); 10 default AnyResultSet getResultSet(AnyQuery query) {
11 return getResultSet((Query<?>) query);
12 }
13
14 <T> ResultSet<T> getResultSet(Query<T> query);
9 15
10 boolean hasPendingChanges(); 16 boolean hasPendingChanges();
11 17
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryBuilder.java
index 4364d844..4fdc9210 100644
--- a/subprojects/store/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;
2 2
3import tools.refinery.store.adapter.ModelAdapterBuilder; 3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore; 4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.query.dnf.AnyQuery;
5 6
6import java.util.Collection; 7import java.util.Collection;
7import java.util.List; 8import java.util.List;
8 9
10@SuppressWarnings("UnusedReturnValue")
9public interface ModelQueryBuilder extends ModelAdapterBuilder { 11public interface ModelQueryBuilder extends ModelAdapterBuilder {
10 default ModelQueryBuilder queries(DNF... queries) { 12 default ModelQueryBuilder queries(AnyQuery... queries) {
11 return queries(List.of(queries)); 13 return queries(List.of(queries));
12 } 14 }
13 15
14 default ModelQueryBuilder queries(Collection<DNF> queries) { 16 default ModelQueryBuilder queries(Collection<? extends AnyQuery> queries) {
15 queries.forEach(this::query); 17 queries.forEach(this::query);
16 return this; 18 return this;
17 } 19 }
18 20
19 ModelQueryBuilder query(DNF query); 21 ModelQueryBuilder query(AnyQuery query);
20 22
21 @Override 23 @Override
22 ModelQueryStoreAdapter createStoreAdapter(ModelStore store); 24 ModelQueryStoreAdapter createStoreAdapter(ModelStore store);
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
index ef5a4587..514e582b 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ModelQueryStoreAdapter.java
@@ -2,6 +2,7 @@ package tools.refinery.store.query;
2 2
3import tools.refinery.store.adapter.ModelStoreAdapter; 3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model; 4import tools.refinery.store.model.Model;
5import tools.refinery.store.query.dnf.AnyQuery;
5import tools.refinery.store.query.view.AnyRelationView; 6import tools.refinery.store.query.view.AnyRelationView;
6 7
7import java.util.Collection; 8import java.util.Collection;
@@ -9,7 +10,7 @@ import java.util.Collection;
9public interface ModelQueryStoreAdapter extends ModelStoreAdapter { 10public interface ModelQueryStoreAdapter extends ModelStoreAdapter {
10 Collection<AnyRelationView> getRelationViews(); 11 Collection<AnyRelationView> getRelationViews();
11 12
12 Collection<DNF> getQueries(); 13 Collection<AnyQuery> getQueries();
13 14
14 @Override 15 @Override
15 ModelQueryAdapter createModelAdapter(Model model); 16 ModelQueryAdapter createModelAdapter(Model model);
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
new file mode 100644
index 00000000..3f6bc06f
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/ResultSet.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.query.dnf.Query;
5import tools.refinery.store.tuple.TupleLike;
6
7public non-sealed interface ResultSet<T> extends AnyResultSet {
8 Query<T> getQuery();
9
10 T get(TupleLike parameters);
11
12 Cursor<TupleLike, T> getAll();
13}
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 @@
1package tools.refinery.store.query.dnf;
2
3public sealed interface AnyQuery permits Query {
4 String name();
5
6 int arity();
7
8 Class<?> valueType();
9
10 Dnf getDnf();
11}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.equality.DnfEqualityChecker;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.Constraint;
6import tools.refinery.store.query.literal.LiteralReduction;
7import tools.refinery.store.query.term.Sort;
8import tools.refinery.store.query.term.Variable;
9
10import java.util.Collection;
11import java.util.HashSet;
12import java.util.List;
13import java.util.Set;
14
15public final class Dnf implements Constraint {
16 private static final String INDENTATION = " ";
17
18 private final String name;
19
20 private final String uniqueName;
21
22 private final List<Variable> parameters;
23
24 private final List<FunctionalDependency<Variable>> functionalDependencies;
25
26 private final List<DnfClause> clauses;
27
28 Dnf(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies,
29 List<DnfClause> clauses) {
30 validateFunctionalDependencies(parameters, functionalDependencies);
31 this.name = name;
32 this.uniqueName = DnfUtils.generateUniqueName(name);
33 this.parameters = parameters;
34 this.functionalDependencies = functionalDependencies;
35 this.clauses = clauses;
36 }
37
38 private static void validateFunctionalDependencies(
39 Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) {
40 var parameterSet = new HashSet<>(parameters);
41 for (var functionalDependency : functionalDependencies) {
42 validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency);
43 validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency);
44 }
45 }
46
47 private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet,
48 Collection<Variable> toValidate,
49 FunctionalDependency<Variable> functionalDependency) {
50 for (var variable : toValidate) {
51 if (!parameterSet.contains(variable)) {
52 throw new IllegalArgumentException(
53 "Variable %s of functional dependency %s does not appear in the parameter list %s"
54 .formatted(variable, functionalDependency, parameters));
55 }
56 }
57 }
58
59 @Override
60 public String name() {
61 return name == null ? uniqueName : name;
62 }
63
64 public boolean isExplicitlyNamed() {
65 return name == null;
66 }
67
68 public String getUniqueName() {
69 return uniqueName;
70 }
71
72 public List<Variable> getParameters() {
73 return parameters;
74 }
75
76 @Override
77 public List<Sort> getSorts() {
78 return parameters.stream().map(Variable::getSort).toList();
79 }
80
81 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
82 return functionalDependencies;
83 }
84
85 @Override
86 public int arity() {
87 return parameters.size();
88 }
89
90 public List<DnfClause> getClauses() {
91 return clauses;
92 }
93
94 public RelationalQuery asRelation() {
95 return new RelationalQuery(this);
96 }
97
98 public <T> FunctionalQuery<T> asFunction(Class<T> type) {
99 return new FunctionalQuery<>(this, type);
100 }
101
102 @Override
103 public LiteralReduction getReduction() {
104 if (clauses.isEmpty()) {
105 return LiteralReduction.ALWAYS_FALSE;
106 }
107 for (var clause : clauses) {
108 if (clause.literals().isEmpty()) {
109 return LiteralReduction.ALWAYS_TRUE;
110 }
111 }
112 return LiteralReduction.NOT_REDUCIBLE;
113 }
114
115 public boolean equalsWithSubstitution(DnfEqualityChecker callEqualityChecker, Dnf other) {
116 if (arity() != other.arity()) {
117 return false;
118 }
119 int numClauses = clauses.size();
120 if (numClauses != other.clauses.size()) {
121 return false;
122 }
123 for (int i = 0; i < numClauses; i++) {
124 var literalEqualityHelper = new LiteralEqualityHelper(callEqualityChecker, parameters, other.parameters);
125 if (!clauses.get(i).equalsWithSubstitution(literalEqualityHelper, other.clauses.get(i))) {
126 return false;
127 }
128 }
129 return true;
130 }
131
132 @Override
133 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
134 if (other instanceof Dnf otherDnf) {
135 return helper.dnfEqual(this, otherDnf);
136 }
137 return false;
138 }
139
140 @Override
141 public String toString() {
142 return "%s/%d".formatted(name, arity());
143 }
144
145 @Override
146 public String toReferenceString() {
147 return "@Dnf " + name;
148 }
149
150 public String toDefinitionString() {
151 var builder = new StringBuilder();
152 builder.append("pred ").append(name()).append("(");
153 var parameterIterator = parameters.iterator();
154 if (parameterIterator.hasNext()) {
155 builder.append(parameterIterator.next());
156 while (parameterIterator.hasNext()) {
157 builder.append(", ").append(parameterIterator.next());
158 }
159 }
160 builder.append(") <->");
161 var clauseIterator = clauses.iterator();
162 if (clauseIterator.hasNext()) {
163 appendClause(clauseIterator.next(), builder);
164 while (clauseIterator.hasNext()) {
165 builder.append("\n;");
166 appendClause(clauseIterator.next(), builder);
167 }
168 } else {
169 builder.append("\n").append(INDENTATION).append("<no clauses>");
170 }
171 builder.append(".\n");
172 return builder.toString();
173 }
174
175 private static void appendClause(DnfClause clause, StringBuilder builder) {
176 var iterator = clause.literals().iterator();
177 if (!iterator.hasNext()) {
178 builder.append("\n").append(INDENTATION).append("<empty>");
179 return;
180 }
181 builder.append("\n").append(INDENTATION).append(iterator.next());
182 while (iterator.hasNext()) {
183 builder.append(",\n").append(INDENTATION).append(iterator.next());
184 }
185 }
186
187 public static DnfBuilder builder() {
188 return builder(null);
189 }
190
191 public static DnfBuilder builder(String name) {
192 return new DnfBuilder(name);
193 }
194}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.*;
8
9@SuppressWarnings("UnusedReturnValue")
10public final class DnfBuilder {
11 private final String name;
12
13 private final List<Variable> parameters = new ArrayList<>();
14
15 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
16
17 private final List<List<Literal>> clauses = new ArrayList<>();
18
19 DnfBuilder(String name) {
20 this.name = name;
21 }
22
23 public DnfBuilder parameter(Variable variable) {
24 if (parameters.contains(variable)) {
25 throw new IllegalArgumentException("Duplicate parameter: " + variable);
26 }
27 parameters.add(variable);
28 return this;
29 }
30
31 public DnfBuilder parameters(Variable... variables) {
32 return parameters(List.of(variables));
33 }
34
35 public DnfBuilder parameters(Collection<? extends Variable> variables) {
36 parameters.addAll(variables);
37 return this;
38 }
39
40 public DnfBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
41 this.functionalDependencies.addAll(functionalDependencies);
42 return this;
43 }
44
45 public DnfBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
46 functionalDependencies.add(functionalDependency);
47 return this;
48 }
49
50 public DnfBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
51 return functionalDependency(new FunctionalDependency<>(Set.copyOf(forEach), Set.copyOf(unique)));
52 }
53
54 public DnfBuilder clause(Literal... literals) {
55 clause(List.of(literals));
56 return this;
57 }
58
59 public DnfBuilder clause(Collection<? extends Literal> literals) {
60 // Remove duplicates by using a hashed data structure.
61 var filteredLiterals = new LinkedHashSet<Literal>(literals.size());
62 for (var literal : literals) {
63 var reduction = literal.getReduction();
64 switch (reduction) {
65 case NOT_REDUCIBLE -> filteredLiterals.add(literal);
66 case ALWAYS_TRUE -> {
67 // Literals reducible to {@code true} can be omitted, because the model is always assumed to have at
68 // least on object.
69 }
70 case ALWAYS_FALSE -> {
71 // Clauses with {@code false} literals can be omitted entirely.
72 return this;
73 }
74 default -> throw new IllegalArgumentException("Invalid reduction: " + reduction);
75 }
76 }
77 clauses.add(List.copyOf(filteredLiterals));
78 return this;
79 }
80
81 public Dnf build() {
82 var postProcessedClauses = postProcessClauses();
83 return new Dnf(name, Collections.unmodifiableList(parameters),
84 Collections.unmodifiableList(functionalDependencies),
85 Collections.unmodifiableList(postProcessedClauses));
86 }
87
88 <T> void output(DataVariable<T> outputVariable) {
89 functionalDependency(Set.copyOf(parameters), Set.of(outputVariable));
90 parameter(outputVariable);
91 }
92
93 private List<DnfClause> postProcessClauses() {
94 var postProcessedClauses = new ArrayList<DnfClause>(clauses.size());
95 for (var literals : clauses) {
96 if (literals.isEmpty()) {
97 // Predicate will always match, the other clauses are irrelevant.
98 return List.of(new DnfClause(Set.of(), List.of()));
99 }
100 var variables = new HashSet<Variable>();
101 for (var literal : literals) {
102 variables.addAll(literal.getBoundVariables());
103 }
104 parameters.forEach(variables::remove);
105 postProcessedClauses.add(new DnfClause(Collections.unmodifiableSet(variables),
106 Collections.unmodifiableList(literals)));
107 }
108 return postProcessedClauses;
109 }
110}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.literal.Literal;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.List;
8import java.util.Set;
9
10public record DnfClause(Set<Variable> boundVariables, List<Literal> literals) {
11 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, DnfClause other) {
12 int size = literals.size();
13 if (size != other.literals.size()) {
14 return false;
15 }
16 for (int i = 0; i < size; i++) {
17 if (!literals.get(i).equalsWithSubstitution(helper, other.literals.get(i))) {
18 return false;
19 }
20 }
21 return true;
22 }
23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
index 0ef77d49..9bcf944c 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/DNFUtils.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/DnfUtils.java
@@ -1,9 +1,9 @@
1package tools.refinery.store.query; 1package tools.refinery.store.query.dnf;
2 2
3import java.util.UUID; 3import java.util.UUID;
4 4
5public final class DNFUtils { 5public final class DnfUtils {
6 private DNFUtils() { 6 private DnfUtils() {
7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); 7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
8 } 8 }
9 9
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
index 63a81713..f4cd109f 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/FunctionalDependency.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/dnf/FunctionalDependency.java
@@ -1,4 +1,4 @@
1package tools.refinery.store.query; 1package tools.refinery.store.query.dnf;
2 2
3import java.util.HashSet; 3import java.util.HashSet;
4import java.util.Set; 4import java.util.Set;
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.CallPolarity;
4import tools.refinery.store.query.term.*;
5
6import java.util.ArrayList;
7import java.util.List;
8import java.util.Objects;
9
10public final class FunctionalQuery<T> implements Query<T> {
11 private final Dnf dnf;
12 private final Class<T> type;
13
14 FunctionalQuery(Dnf dnf, Class<T> type) {
15 var parameters = dnf.getParameters();
16 int outputIndex = dnf.arity() - 1;
17 for (int i = 0; i < outputIndex; i++) {
18 var parameter = parameters.get(i);
19 if (!(parameter instanceof NodeVariable)) {
20 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
21 .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort()));
22 }
23 }
24 var outputParameter = parameters.get(outputIndex);
25 if (!(outputParameter instanceof DataVariable<?> dataOutputParameter) ||
26 !dataOutputParameter.getType().equals(type)) {
27 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
28 .formatted(outputParameter, dnf, type, outputParameter.getSort()));
29 }
30 this.dnf = dnf;
31 this.type = type;
32 }
33
34 @Override
35 public String name() {
36 return dnf.name();
37 }
38
39 @Override
40 public int arity() {
41 return dnf.arity() - 1;
42 }
43
44 @Override
45 public Class<T> valueType() {
46 return type;
47 }
48
49 @Override
50 public T defaultValue() {
51 return null;
52 }
53
54 @Override
55 public Dnf getDnf() {
56 return dnf;
57 }
58
59 public AssignedValue<T> call(List<NodeVariable> arguments) {
60 return targetVariable -> {
61 var argumentsWithTarget = new ArrayList<Variable>(arguments.size() + 1);
62 argumentsWithTarget.addAll(arguments);
63 argumentsWithTarget.add(targetVariable);
64 return dnf.call(CallPolarity.POSITIVE, argumentsWithTarget);
65 };
66 }
67
68 public AssignedValue<T> call(NodeVariable... arguments) {
69 return call(List.of(arguments));
70 }
71
72 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, List<NodeVariable> arguments) {
73 return targetVariable -> {
74 var placeholderVariable = Variable.of(type);
75 var argumentsWithPlaceholder = new ArrayList<Variable>(arguments.size() + 1);
76 argumentsWithPlaceholder.addAll(arguments);
77 argumentsWithPlaceholder.add(placeholderVariable);
78 return dnf.aggregate(placeholderVariable, aggregator, argumentsWithPlaceholder).toLiteral(targetVariable);
79 };
80 }
81
82 public <R> AssignedValue<R> aggregate(Aggregator<R, T> aggregator, NodeVariable... arguments) {
83 return aggregate(aggregator, List.of(arguments));
84 }
85
86 @Override
87 public boolean equals(Object o) {
88 if (this == o) return true;
89 if (o == null || getClass() != o.getClass()) return false;
90 FunctionalQuery<?> that = (FunctionalQuery<?>) o;
91 return dnf.equals(that.dnf) && type.equals(that.type);
92 }
93
94 @Override
95 public int hashCode() {
96 return Objects.hash(dnf, type);
97 }
98
99 @Override
100 public String toString() {
101 return dnf.toString();
102 }
103}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.Variable;
5
6import java.util.Collection;
7import java.util.Set;
8
9public final class FunctionalQueryBuilder<T> {
10 private final DnfBuilder dnfBuilder;
11 private final Class<T> type;
12
13 FunctionalQueryBuilder(DnfBuilder dnfBuilder, Class<T> type) {
14 this.dnfBuilder = dnfBuilder;
15 this.type = type;
16 }
17
18 public FunctionalQueryBuilder<T> functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
19 dnfBuilder.functionalDependencies(functionalDependencies);
20 return this;
21 }
22
23 public FunctionalQueryBuilder<T> functionalDependency(FunctionalDependency<Variable> functionalDependency) {
24 dnfBuilder.functionalDependency(functionalDependency);
25 return this;
26 }
27
28 public FunctionalQueryBuilder<T> functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
29 dnfBuilder.functionalDependency(forEach, unique);
30 return this;
31 }
32
33 public FunctionalQueryBuilder<T> clause(Literal... literals) {
34 dnfBuilder.clause(literals);
35 return this;
36 }
37
38 public FunctionalQueryBuilder<T> clause(Collection<? extends Literal> literals) {
39 dnfBuilder.clause(literals);
40 return this;
41 }
42
43 public FunctionalQuery<T> build() {
44 return dnfBuilder.build().asFunction(type);
45 }
46}
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 @@
1package tools.refinery.store.query.dnf;
2
3public sealed interface Query<T> extends AnyQuery permits RelationalQuery, FunctionalQuery {
4 @Override
5 Class<T> valueType();
6
7 T defaultValue();
8
9 static QueryBuilder builder() {
10 return new QueryBuilder();
11 }
12
13 static QueryBuilder builder(String name) {
14 return new QueryBuilder(name);
15 }
16}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.Literal;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.Collection;
9import java.util.List;
10import java.util.Set;
11
12public final class QueryBuilder {
13 private final DnfBuilder dnfBuilder;
14
15 QueryBuilder(String name) {
16 dnfBuilder = Dnf.builder(name);
17 }
18
19 QueryBuilder() {
20 dnfBuilder = Dnf.builder();
21 }
22
23 public QueryBuilder parameter(NodeVariable variable) {
24 dnfBuilder.parameter(variable);
25 return this;
26 }
27
28 public QueryBuilder parameters(NodeVariable... variables) {
29 dnfBuilder.parameters(variables);
30 return this;
31 }
32
33 public QueryBuilder parameters(List<NodeVariable> variables) {
34 dnfBuilder.parameters(variables);
35 return this;
36 }
37
38 public <T> FunctionalQueryBuilder<T> output(DataVariable<T> outputVariable) {
39 dnfBuilder.output(outputVariable);
40 return new FunctionalQueryBuilder<>(dnfBuilder, outputVariable.getType());
41 }
42
43 public QueryBuilder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
44 dnfBuilder.functionalDependencies(functionalDependencies);
45 return this;
46 }
47
48 public QueryBuilder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
49 dnfBuilder.functionalDependency(functionalDependency);
50 return this;
51 }
52
53 public QueryBuilder functionalDependency(Set<? extends Variable> forEach, Set<? extends Variable> unique) {
54 dnfBuilder.functionalDependency(forEach, unique);
55 return this;
56 }
57
58 public QueryBuilder clause(Literal... literals) {
59 dnfBuilder.clause(literals);
60 return this;
61 }
62
63 public QueryBuilder clause(Collection<? extends Literal> literals) {
64 dnfBuilder.clause(literals);
65 return this;
66 }
67
68 public RelationalQuery build() {
69 return dnfBuilder.build().asRelation();
70 }
71}
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 @@
1package tools.refinery.store.query.dnf;
2
3import tools.refinery.store.query.literal.CallLiteral;
4import tools.refinery.store.query.literal.CallPolarity;
5import tools.refinery.store.query.term.AssignedValue;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.NodeVariable;
8
9import java.util.Collections;
10import java.util.List;
11import java.util.Objects;
12
13public final class RelationalQuery implements Query<Boolean> {
14 private final Dnf dnf;
15
16 RelationalQuery(Dnf dnf) {
17 for (var parameter : dnf.getParameters()) {
18 if (!(parameter instanceof NodeVariable)) {
19 throw new IllegalArgumentException("Expected parameter %s of %s to be of sort %s, but got %s instead"
20 .formatted(parameter, dnf, NodeSort.INSTANCE, parameter.getSort()));
21 }
22 }
23 this.dnf = dnf;
24 }
25
26 @Override
27 public String name() {
28 return dnf.name();
29 }
30
31 @Override
32 public int arity() {
33 return dnf.arity();
34 }
35
36 @Override
37 public Class<Boolean> valueType() {
38 return Boolean.class;
39 }
40
41 @Override
42 public Boolean defaultValue() {
43 return false;
44 }
45
46 @Override
47 public Dnf getDnf() {
48 return dnf;
49 }
50
51 public CallLiteral call(CallPolarity polarity, List<NodeVariable> arguments) {
52 return dnf.call(polarity, Collections.unmodifiableList(arguments));
53 }
54
55 public CallLiteral call(CallPolarity polarity, NodeVariable... arguments) {
56 return dnf.call(polarity, arguments);
57 }
58
59 public CallLiteral call(NodeVariable... arguments) {
60 return dnf.call(arguments);
61 }
62
63 public CallLiteral callTransitive(NodeVariable left, NodeVariable right) {
64 return dnf.callTransitive(left, right);
65 }
66
67 public AssignedValue<Integer> count(List<NodeVariable> arguments) {
68 return dnf.count(Collections.unmodifiableList(arguments));
69 }
70
71 public AssignedValue<Integer> count(NodeVariable... arguments) {
72 return dnf.count(arguments);
73 }
74
75
76 @Override
77 public boolean equals(Object o) {
78 if (this == o) return true;
79 if (o == null || getClass() != o.getClass()) return false;
80 RelationalQuery that = (RelationalQuery) o;
81 return dnf.equals(that.dnf);
82 }
83
84 @Override
85 public int hashCode() {
86 return Objects.hash(dnf);
87 }
88
89 @Override
90 public String toString() {
91 return dnf.toString();
92 }
93}
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
new file mode 100644
index 00000000..c3bc3ea3
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DeepDnfEqualityChecker.java
@@ -0,0 +1,31 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4import tools.refinery.store.util.CycleDetectingMapper;
5
6import java.util.List;
7
8public class DeepDnfEqualityChecker implements DnfEqualityChecker {
9 private final CycleDetectingMapper<Pair, Boolean> mapper = new CycleDetectingMapper<>(Pair::toString,
10 this::doCheckEqual);
11
12 @Override
13 public boolean dnfEqual(Dnf left, Dnf right) {
14 return mapper.map(new Pair(left, right));
15 }
16
17 protected boolean doCheckEqual(Pair pair) {
18 return pair.left.equalsWithSubstitution(this, pair.right);
19 }
20
21 protected List<Pair> getInProgress() {
22 return mapper.getInProgress();
23 }
24
25 protected record Pair(Dnf left, Dnf right) {
26 @Override
27 public String toString() {
28 return "(%s, %s)".formatted(left.name(), right.name());
29 }
30 }
31}
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
new file mode 100644
index 00000000..6b1f2076
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/DnfEqualityChecker.java
@@ -0,0 +1,8 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4
5@FunctionalInterface
6public interface DnfEqualityChecker {
7 boolean dnfEqual(Dnf left, Dnf right);
8}
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
new file mode 100644
index 00000000..07d261ea
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/equality/LiteralEqualityHelper.java
@@ -0,0 +1,48 @@
1package tools.refinery.store.query.equality;
2
3import tools.refinery.store.query.dnf.Dnf;
4import tools.refinery.store.query.term.Variable;
5
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9
10public class LiteralEqualityHelper {
11 private final DnfEqualityChecker dnfEqualityChecker;
12 private final Map<Variable, Variable> leftToRight;
13 private final Map<Variable, Variable> rightToLeft;
14
15 public LiteralEqualityHelper(DnfEqualityChecker dnfEqualityChecker, List<Variable> leftParameters,
16 List<Variable> rightParameters) {
17 this.dnfEqualityChecker = dnfEqualityChecker;
18 var arity = leftParameters.size();
19 if (arity != rightParameters.size()) {
20 throw new IllegalArgumentException("Parameter lists have unequal length");
21 }
22 leftToRight = new HashMap<>(arity);
23 rightToLeft = new HashMap<>(arity);
24 for (int i = 0; i < arity; i++) {
25 if (!variableEqual(leftParameters.get(i), rightParameters.get(i))) {
26 throw new IllegalArgumentException("Parameter lists cannot be unified: duplicate parameter " + i);
27 }
28 }
29 }
30
31 public boolean dnfEqual(Dnf left, Dnf right) {
32 return dnfEqualityChecker.dnfEqual(left, right);
33 }
34
35 public boolean variableEqual(Variable left, Variable right) {
36 if (checkMapping(leftToRight, left, right) && checkMapping(rightToLeft, right, left)) {
37 leftToRight.put(left, right);
38 rightToLeft.put(right, left);
39 return true;
40 }
41 return false;
42 }
43
44 private static boolean checkMapping(Map<Variable, Variable> map, Variable key, Variable expectedValue) {
45 var currentValue = map.get(key);
46 return currentValue == null || currentValue.equals(expectedValue);
47 }
48}
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 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.List;
9import java.util.Objects;
10
11public abstract class AbstractCallLiteral implements Literal {
12 private final Constraint target;
13 private final List<Variable> arguments;
14
15 protected AbstractCallLiteral(Constraint target, List<Variable> arguments) {
16 int arity = target.arity();
17 if (arguments.size() != arity) {
18 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
19 target.arity(), arguments.size()));
20 }
21 this.target = target;
22 this.arguments = arguments;
23 var sorts = target.getSorts();
24 for (int i = 0; i < arity; i++) {
25 var argument = arguments.get(i);
26 var sort = sorts.get(i);
27 if (!sort.isInstance(argument)) {
28 throw new IllegalArgumentException("Required argument %d of %s to be of sort %s, but got %s instead"
29 .formatted(i, target, sort, argument.getSort()));
30 }
31 }
32 }
33
34 public Constraint getTarget() {
35 return target;
36 }
37
38 public List<Variable> getArguments() {
39 return arguments;
40 }
41
42 @Override
43 public Literal substitute(Substitution substitution) {
44 var substitutedArguments = arguments.stream().map(substitution::getSubstitute).toList();
45 return doSubstitute(substitution, substitutedArguments);
46 }
47
48 protected abstract Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments);
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 if (other == null || getClass() != other.getClass()) {
53 return false;
54 }
55 var otherCallLiteral = (AbstractCallLiteral) other;
56 var arity = arguments.size();
57 if (arity != otherCallLiteral.arguments.size()) {
58 return false;
59 }
60 for (int i = 0; i < arity; i++) {
61 if (!helper.variableEqual(arguments.get(i), otherCallLiteral.arguments.get(i))) {
62 return false;
63 }
64 }
65 return target.equals(helper, otherCallLiteral.target);
66 }
67
68 @Override
69 public boolean equals(Object o) {
70 if (this == o) return true;
71 if (o == null || getClass() != o.getClass()) return false;
72 AbstractCallLiteral that = (AbstractCallLiteral) o;
73 return target.equals(that.target) && arguments.equals(that.arguments);
74 }
75
76 @Override
77 public int hashCode() {
78 return Objects.hash(target, arguments);
79 }
80}
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 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.Aggregator;
7import tools.refinery.store.query.term.DataVariable;
8import tools.refinery.store.query.term.Variable;
9
10import java.util.List;
11import java.util.Objects;
12import java.util.Set;
13
14public class AggregationLiteral<R, T> extends AbstractCallLiteral {
15 private final DataVariable<R> resultVariable;
16 private final DataVariable<T> inputVariable;
17 private final Aggregator<R, T> aggregator;
18
19 public AggregationLiteral(DataVariable<R> resultVariable, Aggregator<R, T> aggregator,
20 DataVariable<T> inputVariable, Constraint target, List<Variable> arguments) {
21 super(target, arguments);
22 if (!inputVariable.getType().equals(aggregator.getInputType())) {
23 throw new IllegalArgumentException("Input variable %s must of type %s, got %s instead".formatted(
24 inputVariable, aggregator.getInputType().getName(), inputVariable.getType().getName()));
25 }
26 if (!resultVariable.getType().equals(aggregator.getResultType())) {
27 throw new IllegalArgumentException("Result variable %s must of type %s, got %s instead".formatted(
28 resultVariable, aggregator.getResultType().getName(), resultVariable.getType().getName()));
29 }
30 if (!arguments.contains(inputVariable)) {
31 throw new IllegalArgumentException("Input variable %s must appear in the argument list".formatted(
32 inputVariable));
33 }
34 if (arguments.contains(resultVariable)) {
35 throw new IllegalArgumentException("Result variable %s must not appear in the argument list".formatted(
36 resultVariable));
37 }
38 this.resultVariable = resultVariable;
39 this.inputVariable = inputVariable;
40 this.aggregator = aggregator;
41 }
42
43 public DataVariable<R> getResultVariable() {
44 return resultVariable;
45 }
46
47 public DataVariable<T> getInputVariable() {
48 return inputVariable;
49 }
50
51 public Aggregator<R, T> getAggregator() {
52 return aggregator;
53 }
54
55 @Override
56 public Set<Variable> getBoundVariables() {
57 return Set.of(resultVariable);
58 }
59
60 @Override
61 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
62 return new AggregationLiteral<>(substitution.getTypeSafeSubstitute(resultVariable), aggregator,
63 substitution.getTypeSafeSubstitute(inputVariable), getTarget(), substitutedArguments);
64 }
65
66 @Override
67 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
68 if (!super.equalsWithSubstitution(helper, other)) {
69 return false;
70 }
71 var otherAggregationLiteral = (AggregationLiteral<?, ?>) other;
72 return helper.variableEqual(resultVariable, otherAggregationLiteral.resultVariable) &&
73 aggregator.equals(otherAggregationLiteral.aggregator) &&
74 helper.variableEqual(inputVariable, otherAggregationLiteral.inputVariable);
75 }
76
77 @Override
78 public boolean equals(Object o) {
79 if (this == o) return true;
80 if (o == null || getClass() != o.getClass()) return false;
81 if (!super.equals(o)) return false;
82 AggregationLiteral<?, ?> that = (AggregationLiteral<?, ?>) o;
83 return resultVariable.equals(that.resultVariable) && inputVariable.equals(that.inputVariable) &&
84 aggregator.equals(that.aggregator);
85 }
86
87 @Override
88 public int hashCode() {
89 return Objects.hash(super.hashCode(), resultVariable, inputVariable, aggregator);
90 }
91
92 @Override
93 public String toString() {
94 var builder = new StringBuilder();
95 builder.append(resultVariable);
96 builder.append(" is ");
97 builder.append(getTarget().toReferenceString());
98 builder.append("(");
99 var argumentIterator = getArguments().iterator();
100 if (argumentIterator.hasNext()) {
101 var argument = argumentIterator.next();
102 if (inputVariable.equals(argument)) {
103 builder.append("@Aggregate(\"").append(aggregator).append("\") ");
104 }
105 builder.append(argument);
106 while (argumentIterator.hasNext()) {
107 builder.append(", ").append(argumentIterator.next());
108 }
109 }
110 builder.append(")");
111 return builder.toString();
112 }
113}
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 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.DataVariable;
6import tools.refinery.store.query.term.Term;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.Set;
10
11public record AssignLiteral<T>(DataVariable<T> variable, Term<T> term) implements Literal {
12 public AssignLiteral {
13 if (!term.getType().equals(variable.getType())) {
14 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
15 term, variable.getType().getName(), term.getType().getName()));
16 }
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of(variable);
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 return new AssignLiteral<>(substitution.getTypeSafeSubstitute(variable), term.substitute(substitution));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
31 if (other == null || getClass() != other.getClass()) {
32 return false;
33 }
34 var otherLetLiteral = (AssignLiteral<?>) other;
35 return helper.variableEqual(variable, otherLetLiteral.variable) && term.equalsWithSubstitution(helper,
36 otherLetLiteral.term);
37 }
38
39
40 @Override
41 public String toString() {
42 return "%s is (%s)".formatted(variable, term);
43 }
44}
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 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.Term;
6import tools.refinery.store.query.term.Variable;
7import tools.refinery.store.query.term.bool.BoolConstantTerm;
8
9import java.util.Set;
10
11public record AssumeLiteral(Term<Boolean> term) implements Literal {
12 public AssumeLiteral {
13 if (!term.getType().equals(Boolean.class)) {
14 throw new IllegalArgumentException("Term %s must be of type %s, got %s instead".formatted(
15 term, Boolean.class.getName(), term.getType().getName()));
16 }
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of();
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 return new AssumeLiteral(term.substitute(substitution));
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
31 if (other == null || getClass() != other.getClass()) {
32 return false;
33 }
34 var otherAssumeLiteral = (AssumeLiteral) other;
35 return term.equalsWithSubstitution(helper, otherAssumeLiteral.term);
36 }
37
38 @Override
39 public LiteralReduction getReduction() {
40 if (BoolConstantTerm.TRUE.equals(term)) {
41 return LiteralReduction.ALWAYS_TRUE;
42 } else if (BoolConstantTerm.FALSE.equals(term)) {
43 return LiteralReduction.ALWAYS_FALSE;
44 } else {
45 return LiteralReduction.NOT_REDUCIBLE;
46 }
47 }
48
49 @Override
50 public String toString() {
51 return "(%s)".formatted(term);
52 }
53}
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
new file mode 100644
index 00000000..38be61a4
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/BooleanLiteral.java
@@ -0,0 +1,53 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Variable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6
7import java.util.Set;
8
9public enum BooleanLiteral implements CanNegate<BooleanLiteral> {
10 TRUE(true),
11 FALSE(false);
12
13 private final boolean value;
14
15 BooleanLiteral(boolean value) {
16 this.value = value;
17 }
18
19 @Override
20 public Set<Variable> getBoundVariables() {
21 return Set.of();
22 }
23
24 @Override
25 public Literal substitute(Substitution substitution) {
26 // No variables to substitute.
27 return this;
28 }
29
30 @Override
31 public LiteralReduction getReduction() {
32 return value ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
33 }
34
35 @Override
36 public BooleanLiteral negate() {
37 return fromBoolean(!value);
38 }
39
40 @Override
41 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
42 return equals(other);
43 }
44
45 @Override
46 public String toString() {
47 return Boolean.toString(value);
48 }
49
50 public static BooleanLiteral fromBoolean(boolean value) {
51 return value ? TRUE : FALSE;
52 }
53}
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
new file mode 100644
index 00000000..78fae7f5
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallLiteral.java
@@ -0,0 +1,102 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.List;
10import java.util.Objects;
11import java.util.Set;
12
13public final class CallLiteral extends AbstractCallLiteral implements CanNegate<CallLiteral> {
14 private final CallPolarity polarity;
15
16 public CallLiteral(CallPolarity polarity, Constraint target, List<Variable> arguments) {
17 super(target, arguments);
18 if (polarity.isTransitive()) {
19 if (target.arity() != 2) {
20 throw new IllegalArgumentException("Transitive closures can only take binary relations");
21 }
22 var sorts = target.getSorts();
23 if (!sorts.get(0).equals(NodeSort.INSTANCE) || !sorts.get(1).equals(NodeSort.INSTANCE)) {
24 throw new IllegalArgumentException("Transitive closures can only be computed over nodes");
25 }
26 }
27 this.polarity = polarity;
28 }
29
30 public CallPolarity getPolarity() {
31 return polarity;
32 }
33
34 @Override
35 public Set<Variable> getBoundVariables() {
36 return polarity.isPositive() ? Set.copyOf(getArguments()) : Set.of();
37 }
38
39 @Override
40 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
41 return new CallLiteral(polarity, getTarget(), substitutedArguments);
42 }
43
44 @Override
45 public LiteralReduction getReduction() {
46 var reduction = getTarget().getReduction();
47 return polarity.isPositive() ? reduction : reduction.negate();
48 }
49
50 @Override
51 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
52 if (!super.equalsWithSubstitution(helper, other)) {
53 return false;
54 }
55 var otherCallLiteral = (CallLiteral) other;
56 return polarity.equals(otherCallLiteral.polarity);
57 }
58
59 @Override
60 public CallLiteral negate() {
61 return new CallLiteral(polarity.negate(), getTarget(), getArguments());
62 }
63
64 @Override
65 public boolean equals(Object o) {
66 if (this == o) return true;
67 if (o == null || getClass() != o.getClass()) return false;
68 if (!super.equals(o)) return false;
69 CallLiteral that = (CallLiteral) o;
70 return polarity == that.polarity;
71 }
72
73 @Override
74 public int hashCode() {
75 return Objects.hash(super.hashCode(), polarity);
76 }
77
78 @Override
79 public String toString() {
80 var builder = new StringBuilder();
81 if (!polarity.isPositive()) {
82 builder.append("!(");
83 }
84 builder.append(getTarget().toReferenceString());
85 if (polarity.isTransitive()) {
86 builder.append("+");
87 }
88 builder.append("(");
89 var argumentIterator = getArguments().iterator();
90 if (argumentIterator.hasNext()) {
91 builder.append(argumentIterator.next());
92 while (argumentIterator.hasNext()) {
93 builder.append(", ").append(argumentIterator.next());
94 }
95 }
96 builder.append(")");
97 if (!polarity.isPositive()) {
98 builder.append(")");
99 }
100 return builder.toString();
101 }
102}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
index 957e9b7b..84b4b771 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallPolarity.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/CallPolarity.java
@@ -1,4 +1,4 @@
1package tools.refinery.store.query.atom; 1package tools.refinery.store.query.literal;
2 2
3public enum CallPolarity { 3public enum CallPolarity {
4 POSITIVE(true, false), 4 POSITIVE(true, false),
@@ -22,7 +22,11 @@ public enum CallPolarity {
22 return transitive; 22 return transitive;
23 } 23 }
24 24
25 public static CallPolarity fromBoolean(boolean positive) { 25 public CallPolarity negate() {
26 return positive ? POSITIVE : NEGATIVE; 26 return switch (this) {
27 case POSITIVE -> NEGATIVE;
28 case NEGATIVE -> POSITIVE;
29 case TRANSITIVE -> throw new IllegalArgumentException("Transitive polarity cannot be negated");
30 };
27 } 31 }
28} 32}
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 @@
1package tools.refinery.store.query.literal;
2
3public interface CanNegate<T extends CanNegate<T>> extends Literal {
4 T negate();
5}
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
new file mode 100644
index 00000000..93fa3df0
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/ConstantLiteral.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8import java.util.Set;
9
10public record ConstantLiteral(NodeVariable variable, int nodeId) implements Literal {
11 @Override
12 public Set<Variable> getBoundVariables() {
13 return Set.of(variable);
14 }
15
16 @Override
17 public ConstantLiteral substitute(Substitution substitution) {
18 return new ConstantLiteral(substitution.getTypeSafeSubstitute(variable), nodeId);
19 }
20
21 @Override
22 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
23 if (other.getClass() != getClass()) {
24 return false;
25 }
26 var otherConstantLiteral = (ConstantLiteral) other;
27 return helper.variableEqual(variable, otherConstantLiteral.variable) && nodeId == otherConstantLiteral.nodeId;
28 }
29
30 @Override
31 public String toString() {
32 return "%s === @Constant %d".formatted(variable, nodeId);
33 }
34}
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 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.term.DataVariable;
7import tools.refinery.store.query.term.Variable;
8
9import java.util.List;
10import java.util.Objects;
11import java.util.Set;
12
13public class CountLiteral extends AbstractCallLiteral {
14 private final DataVariable<Integer> resultVariable;
15
16 public CountLiteral(DataVariable<Integer> resultVariable, Constraint target, List<Variable> arguments) {
17 super(target, arguments);
18 if (!resultVariable.getType().equals(Integer.class)) {
19 throw new IllegalArgumentException("Count result variable %s must be of type %s, got %s instead".formatted(
20 resultVariable, Integer.class.getName(), resultVariable.getType().getName()));
21 }
22 if (arguments.contains(resultVariable)) {
23 throw new IllegalArgumentException("Count result variable %s must not appear in the argument list"
24 .formatted(resultVariable));
25 }
26 this.resultVariable = resultVariable;
27 }
28
29 public DataVariable<Integer> getResultVariable() {
30 return resultVariable;
31 }
32
33 @Override
34 public Set<Variable> getBoundVariables() {
35 return Set.of(resultVariable);
36 }
37
38 @Override
39 protected Literal doSubstitute(Substitution substitution, List<Variable> substitutedArguments) {
40 return new CountLiteral(substitution.getTypeSafeSubstitute(resultVariable), getTarget(), substitutedArguments);
41 }
42
43 @Override
44 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
45 if (!super.equalsWithSubstitution(helper, other)) {
46 return false;
47 }
48 var otherCountLiteral = (CountLiteral) other;
49 return helper.variableEqual(resultVariable, otherCountLiteral.resultVariable);
50 }
51
52 @Override
53 public boolean equals(Object o) {
54 if (this == o) return true;
55 if (o == null || getClass() != o.getClass()) return false;
56 if (!super.equals(o)) return false;
57 CountLiteral that = (CountLiteral) o;
58 return resultVariable.equals(that.resultVariable);
59 }
60
61 @Override
62 public int hashCode() {
63 return Objects.hash(super.hashCode(), resultVariable);
64 }
65
66 @Override
67 public String toString() {
68 var builder = new StringBuilder();
69 builder.append(resultVariable);
70 builder.append(" is count ");
71 builder.append(getTarget().toReferenceString());
72 builder.append("(");
73 var argumentIterator = getArguments().iterator();
74 if (argumentIterator.hasNext()) {
75 builder.append(argumentIterator.next());
76 while (argumentIterator.hasNext()) {
77 builder.append(", ").append(argumentIterator.next());
78 }
79 }
80 builder.append(")");
81 return builder.toString();
82 }
83}
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
new file mode 100644
index 00000000..4dc86b98
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/EquivalenceLiteral.java
@@ -0,0 +1,52 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.NodeVariable;
4import tools.refinery.store.query.term.Variable;
5import tools.refinery.store.query.equality.LiteralEqualityHelper;
6import tools.refinery.store.query.substitution.Substitution;
7
8import java.util.Set;
9
10public record EquivalenceLiteral(boolean positive, NodeVariable left, NodeVariable right)
11 implements CanNegate<EquivalenceLiteral> {
12 @Override
13 public Set<Variable> getBoundVariables() {
14 // If one side of a {@code positive} equivalence is bound, it may bind its other side, but we under-approximate
15 // this behavior by not binding any of the sides by default.
16 return Set.of();
17 }
18
19 @Override
20 public EquivalenceLiteral negate() {
21 return new EquivalenceLiteral(!positive, left, right);
22 }
23
24 @Override
25 public EquivalenceLiteral substitute(Substitution substitution) {
26 return new EquivalenceLiteral(positive, substitution.getTypeSafeSubstitute(left),
27 substitution.getTypeSafeSubstitute(right));
28 }
29
30 @Override
31 public LiteralReduction getReduction() {
32 if (left.equals(right)) {
33 return positive ? LiteralReduction.ALWAYS_TRUE : LiteralReduction.ALWAYS_FALSE;
34 }
35 return LiteralReduction.NOT_REDUCIBLE;
36 }
37
38 @Override
39 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other) {
40 if (other.getClass() != getClass()) {
41 return false;
42 }
43 var otherEquivalenceLiteral = (EquivalenceLiteral) other;
44 return helper.variableEqual(left, otherEquivalenceLiteral.left) && helper.variableEqual(right,
45 otherEquivalenceLiteral.right);
46 }
47
48 @Override
49 public String toString() {
50 return "%s %s %s".formatted(left, positive ? "===" : "!==", right);
51 }
52}
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
new file mode 100644
index 00000000..6347410e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literal.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Variable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.substitution.Substitution;
6
7import java.util.Set;
8
9public interface Literal {
10 Set<Variable> getBoundVariables();
11
12 Literal substitute(Substitution substitution);
13
14 default LiteralReduction getReduction() {
15 return LiteralReduction.NOT_REDUCIBLE;
16 }
17
18 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
19 boolean equalsWithSubstitution(LiteralEqualityHelper helper, Literal other);
20}
diff --git a/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
new file mode 100644
index 00000000..146089f6
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/LiteralReduction.java
@@ -0,0 +1,26 @@
1package tools.refinery.store.query.literal;
2
3public enum LiteralReduction {
4 /**
5 * Signifies that a literal should be preserved in the clause.
6 */
7 NOT_REDUCIBLE,
8
9 /**
10 * Signifies that the literal may be omitted from the cause (if the model being queried is nonempty).
11 */
12 ALWAYS_TRUE,
13
14 /**
15 * Signifies that the clause with the literal may be omitted entirely.
16 */
17 ALWAYS_FALSE;
18
19 public LiteralReduction negate() {
20 return switch (this) {
21 case NOT_REDUCIBLE -> NOT_REDUCIBLE;
22 case ALWAYS_TRUE -> ALWAYS_FALSE;
23 case ALWAYS_FALSE -> ALWAYS_TRUE;
24 };
25 }
26}
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
new file mode 100644
index 00000000..89039352
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/literal/Literals.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.query.literal;
2
3import tools.refinery.store.query.term.Term;
4
5public final class Literals {
6 private Literals() {
7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
8 }
9
10 public static <T extends CanNegate<T>> T not(CanNegate<T> literal) {
11 return literal.negate();
12 }
13
14 public static AssumeLiteral assume(Term<Boolean> term) {
15 return new AssumeLiteral(term);
16 }
17}
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 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5public record CompositeSubstitution(Substitution first, Substitution second) implements Substitution {
6 @Override
7 public Variable getSubstitute(Variable variable) {
8 return second.getSubstitute(first.getSubstitute(variable));
9 }
10}
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
new file mode 100644
index 00000000..c7754619
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/MapBasedSubstitution.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5import java.util.Map;
6
7public record MapBasedSubstitution(Map<Variable, Variable> map, Substitution fallback) implements Substitution {
8 @Override
9 public Variable getSubstitute(Variable variable) {
10 var value = map.get(variable);
11 return value == null ? fallback.getSubstitute(variable) : value;
12 }
13}
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
new file mode 100644
index 00000000..7847e582
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/RenewingSubstitution.java
@@ -0,0 +1,15 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5import java.util.HashMap;
6import java.util.Map;
7
8public class RenewingSubstitution implements Substitution {
9 private final Map<Variable, Variable> alreadyRenewed = new HashMap<>();
10
11 @Override
12 public Variable getSubstitute(Variable variable) {
13 return alreadyRenewed.computeIfAbsent(variable, Variable::renew);
14 }
15}
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
new file mode 100644
index 00000000..eed414d9
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/StatelessSubstitution.java
@@ -0,0 +1,18 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.Variable;
4
5public enum StatelessSubstitution implements Substitution {
6 FAILING {
7 @Override
8 public Variable getSubstitute(Variable variable) {
9 throw new IllegalArgumentException("No substitute for " + variable);
10 }
11 },
12 IDENTITY {
13 @Override
14 public Variable getSubstitute(Variable variable) {
15 return variable;
16 }
17 }
18}
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
new file mode 100644
index 00000000..99f84b9e
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitution.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.query.substitution;
2
3import tools.refinery.store.query.term.AnyDataVariable;
4import tools.refinery.store.query.term.DataVariable;
5import tools.refinery.store.query.term.NodeVariable;
6import tools.refinery.store.query.term.Variable;
7
8@FunctionalInterface
9public interface Substitution {
10 Variable getSubstitute(Variable variable);
11
12 default NodeVariable getTypeSafeSubstitute(NodeVariable variable) {
13 var substitute = getSubstitute(variable);
14 return substitute.asNodeVariable();
15 }
16
17 default AnyDataVariable getTypeSafeSubstitute(AnyDataVariable variable) {
18 return getTypeSafeSubstitute((DataVariable<?>) variable);
19 }
20
21 default <T> DataVariable<T> getTypeSafeSubstitute(DataVariable<T> variable) {
22 var substitute = getSubstitute(variable);
23 return substitute.asDataVariable(variable.getType());
24 }
25
26 default Substitution andThen(Substitution second) {
27 return new CompositeSubstitution(this, second);
28 }
29}
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
new file mode 100644
index 00000000..5d4654da
--- /dev/null
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/substitution/Substitutions.java
@@ -0,0 +1,33 @@
1package tools.refinery.store.query.substitution;
2
3import org.jetbrains.annotations.NotNull;
4import org.jetbrains.annotations.Nullable;
5import tools.refinery.store.query.term.Variable;
6
7import java.util.Map;
8
9public final class Substitutions {
10 private Substitutions() {
11 throw new IllegalStateException("This is a static utility class and should not be instantiate directly");
12 }
13
14 public static Substitution total(Map<Variable, Variable> map) {
15 return new MapBasedSubstitution(map, StatelessSubstitution.FAILING);
16 }
17
18 public static Substitution partial(Map<Variable, Variable> map) {
19 return new MapBasedSubstitution(map, StatelessSubstitution.IDENTITY);
20 }
21
22 public static Substitution renewing(Map<Variable, Variable> map) {
23 return new MapBasedSubstitution(map, renewing());
24 }
25
26 public static Substitution renewing() {
27 return new RenewingSubstitution();
28 }
29
30 public static Substitution compose(@Nullable Substitution first, @NotNull Substitution second) {
31 return first == null ? second : first.andThen(second);
32 }
33}
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 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface Aggregator<R, T> {
6 Class<R> getResultType();
7
8 Class<T> getInputType();
9
10 R aggregateStream(Stream<T> stream);
11
12 R getEmptyResult();
13}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5
6import java.util.Set;
7
8public abstract sealed class AnyDataVariable extends Variable implements AnyTerm permits DataVariable {
9 protected AnyDataVariable(String name) {
10 super(name);
11 }
12
13 @Override
14 public NodeVariable asNodeVariable() {
15 throw new IllegalStateException("%s is a data variable".formatted(this));
16 }
17
18 @Override
19 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
20 return other instanceof AnyDataVariable dataVariable && helper.variableEqual(this, dataVariable);
21 }
22
23 @Override
24 public Set<AnyDataVariable> getInputVariables() {
25 return Set.of(this);
26 }
27
28 @Override
29 public abstract AnyDataVariable renew(@Nullable String name);
30
31 @Override
32 public abstract AnyDataVariable renew();
33}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5
6import java.util.Set;
7
8public sealed interface AnyTerm permits AnyDataVariable, Term {
9 Class<?> getType();
10
11 AnyTerm substitute(Substitution substitution);
12
13 boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other);
14
15 Set<AnyDataVariable> getInputVariables();
16}
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 @@
1package tools.refinery.store.query.term;
2
3public enum ArithmeticBinaryOperator {
4 ADD("+", true),
5 SUB("-", true),
6 MUL("*", true),
7 DIV("/", true),
8 POW("**", true),
9 MIN("min", false),
10 MAX("max", false);
11
12 private final String text;
13 private final boolean infix;
14
15 ArithmeticBinaryOperator(String text, boolean infix) {
16 this.text = text;
17 this.infix = infix;
18 }
19
20 public String formatString(String left, String right) {
21 if (infix) {
22 return "(%s) %s (%s)".formatted(left, text, right);
23 }
24 return "%s(%s, %s)".formatted(text, left, right);
25 }
26}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ArithmeticBinaryTerm<T> extends BinaryTerm<T, T, T> {
8 private final ArithmeticBinaryOperator operator;
9
10 protected ArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<T> left, Term<T> right) {
11 super(left, right);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<T> getLeftType() {
17 return getType();
18 }
19
20 @Override
21 public Class<T> getRightType() {
22 return getType();
23 }
24
25 public ArithmeticBinaryOperator getOperator() {
26 return operator;
27 }
28
29 @Override
30 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
31 if (!super.equalsWithSubstitution(helper, other)) {
32 return false;
33 }
34 var otherArithmeticBinaryTerm = (ArithmeticBinaryTerm<?>) other;
35 return operator == otherArithmeticBinaryTerm.operator;
36 }
37
38 @Override
39 public String toString() {
40 return operator.formatString(getLeft().toString(), getRight().toString());
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 if (!super.equals(o)) return false;
48 ArithmeticBinaryTerm<?> that = (ArithmeticBinaryTerm<?>) o;
49 return operator == that.operator;
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(super.hashCode(), operator);
55 }
56}
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 @@
1package tools.refinery.store.query.term;
2
3public enum ArithmeticUnaryOperator {
4 PLUS("+"),
5 MINUS("-");
6
7 private final String prefix;
8
9 ArithmeticUnaryOperator(String prefix) {
10 this.prefix = prefix;
11 }
12
13 public String formatString(String body) {
14 return "%s(%s)".formatted(prefix, body);
15 }
16}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ArithmeticUnaryTerm<T> extends UnaryTerm<T, T> {
8 private final ArithmeticUnaryOperator operator;
9
10 protected ArithmeticUnaryTerm(ArithmeticUnaryOperator operator, Term<T> body) {
11 super(body);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<T> getBodyType() {
17 return getType();
18 }
19
20 public ArithmeticUnaryOperator getOperator() {
21 return operator;
22 }
23
24 @Override
25 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
26 if (!super.equalsWithSubstitution(helper, other)) {
27 return false;
28 }
29 var otherArithmeticUnaryTerm = (ArithmeticUnaryTerm<?>) other;
30 return operator == otherArithmeticUnaryTerm.operator;
31 }
32
33 @Override
34 public String toString() {
35 return operator.formatString(getBody().toString());
36 }
37
38 @Override
39 public boolean equals(Object o) {
40 if (this == o) return true;
41 if (o == null || getClass() != o.getClass()) return false;
42 if (!super.equals(o)) return false;
43 ArithmeticUnaryTerm<?> that = (ArithmeticUnaryTerm<?>) o;
44 return operator == that.operator;
45 }
46
47 @Override
48 public int hashCode() {
49 return Objects.hash(super.hashCode(), operator);
50 }
51}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.literal.Literal;
4
5@FunctionalInterface
6public interface AssignedValue<T> {
7 Literal toLiteral(DataVariable<T> targetVariable);
8}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Collections;
8import java.util.HashSet;
9import java.util.Objects;
10import java.util.Set;
11
12public abstract class BinaryTerm<R, T1, T2> implements Term<R> {
13 private final Term<T1> left;
14 private final Term<T2> right;
15
16 protected BinaryTerm(Term<T1> left, Term<T2> right) {
17 if (!left.getType().equals(getLeftType())) {
18 throw new IllegalArgumentException("Expected left %s to be of type %s, got %s instead".formatted(left,
19 getLeftType().getName(), left.getType().getName()));
20 }
21 if (!right.getType().equals(getRightType())) {
22 throw new IllegalArgumentException("Expected right %s to be of type %s, got %s instead".formatted(right,
23 getRightType().getName(), right.getType().getName()));
24 }
25 this.left = left;
26 this.right = right;
27 }
28
29 public abstract Class<T1> getLeftType();
30
31 public abstract Class<T2> getRightType();
32
33 public Term<T1> getLeft() {
34 return left;
35 }
36
37 public Term<T2> getRight() {
38 return right;
39 }
40
41 @Override
42 public R evaluate(Valuation valuation) {
43 var leftValue = left.evaluate(valuation);
44 if (leftValue == null) {
45 return null;
46 }
47 var rightValue = right.evaluate(valuation);
48 if (rightValue == null) {
49 return null;
50 }
51 return doEvaluate(leftValue, rightValue);
52 }
53
54 protected abstract R doEvaluate(T1 leftValue, T2 rightValue);
55
56 @Override
57 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
58 if (getClass() != other.getClass()) {
59 return false;
60 }
61 var otherBinaryTerm = (BinaryTerm<?, ?, ?>) other;
62 return left.equalsWithSubstitution(helper, otherBinaryTerm.left) && right.equalsWithSubstitution(helper,
63 otherBinaryTerm.right);
64 }
65
66 @Override
67 public Term<R> substitute(Substitution substitution) {
68 return doSubstitute(substitution, left.substitute(substitution), right.substitute(substitution));
69 }
70
71 public abstract Term<R> doSubstitute(Substitution substitution, Term<T1> substitutedLeft,
72 Term<T2> substitutedRight);
73
74 @Override
75 public Set<AnyDataVariable> getInputVariables() {
76 var inputVariables = new HashSet<>(left.getInputVariables());
77 inputVariables.addAll(right.getInputVariables());
78 return Collections.unmodifiableSet(inputVariables);
79 }
80
81 @Override
82 public boolean equals(Object o) {
83 if (this == o) return true;
84 if (o == null || getClass() != o.getClass()) return false;
85 BinaryTerm<?, ?, ?> that = (BinaryTerm<?, ?, ?>) o;
86 return left.equals(that.left) && right.equals(that.right);
87 }
88
89 @Override
90 public int hashCode() {
91 return Objects.hash(getClass(), left, right);
92 }
93}
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 @@
1package tools.refinery.store.query.term;
2
3public enum ComparisonOperator {
4 EQ("=="),
5 NOT_EQ("!="),
6 LESS("<"),
7 LESS_EQ("<="),
8 GREATER(">"),
9 GREATER_EQ(">=");
10
11 private final String text;
12
13 ComparisonOperator(String text) {
14 this.text = text;
15 }
16
17 public String formatString(String left, String right) {
18 return "(%s) %s (%s)".formatted(left, text, right);
19 }
20}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4
5import java.util.Objects;
6
7public abstract class ComparisonTerm<T> extends BinaryTerm<Boolean, T, T> {
8 private final ComparisonOperator operator;
9
10 protected ComparisonTerm(ComparisonOperator operator, Term<T> left, Term<T> right) {
11 super(left, right);
12 this.operator = operator;
13 }
14
15 @Override
16 public Class<Boolean> getType() {
17 return Boolean.class;
18 }
19
20 public abstract Class<T> getOperandType();
21
22 @Override
23 public Class<T> getLeftType() {
24 return getOperandType();
25 }
26
27 @Override
28 public Class<T> getRightType() {
29 return getOperandType();
30 }
31
32 public ComparisonOperator getOperator() {
33 return operator;
34 }
35
36 @Override
37 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
38 if (!super.equalsWithSubstitution(helper, other)) {
39 return false;
40 }
41 var otherComparisonTerm = (ComparisonTerm<?>) other;
42 return operator == otherComparisonTerm.operator;
43 }
44
45 @Override
46 public String toString() {
47 return operator.formatString(getLeft().toString(), getRight().toString());
48 }
49
50 @Override
51 public boolean equals(Object o) {
52 if (this == o) return true;
53 if (o == null || getClass() != o.getClass()) return false;
54 if (!super.equals(o)) return false;
55 ComparisonTerm<?> that = (ComparisonTerm<?>) o;
56 return operator == that.operator;
57 }
58
59 @Override
60 public int hashCode() {
61 return Objects.hash(super.hashCode(), operator);
62 }
63}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Set;
8
9public record ConstantTerm<T>(Class<T> type, T value) implements Term<T> {
10 public ConstantTerm {
11 if (value == null) {
12 throw new IllegalArgumentException("value should not be null");
13 }
14 if (!type.isInstance(value)) {
15 throw new IllegalArgumentException("value %s is not an instance of %s".formatted(value, type.getName()));
16 }
17 }
18
19 @Override
20 public Class<T> getType() {
21 return type;
22 }
23
24 public T getValue() {
25 return value;
26 }
27
28 @Override
29 public T evaluate(Valuation valuation) {
30 return getValue();
31 }
32
33 @Override
34 public Term<T> substitute(Substitution substitution) {
35 return this;
36 }
37
38 @Override
39 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
40 return equals(other);
41 }
42
43 @Override
44 public Set<AnyDataVariable> getInputVariables() {
45 return Set.of();
46 }
47
48 @Override
49 public String toString() {
50 return getValue().toString();
51 }
52}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public record DataSort<T>(Class<T> type) implements Sort {
6 public static final DataSort<Integer> INT = new DataSort<>(Integer.class);
7
8 public static final DataSort<Boolean> BOOL = new DataSort<>(Boolean.class);
9
10 @Override
11 public boolean isInstance(Variable variable) {
12 return variable instanceof DataVariable<?> dataVariable && type.equals(dataVariable.getType());
13 }
14
15 @Override
16 public DataVariable<T> newInstance(@Nullable String name) {
17 return Variable.of(name, type);
18 }
19
20 @Override
21 public DataVariable<T> newInstance() {
22 return newInstance(null);
23 }
24
25 @Override
26 public String toString() {
27 return type.getName();
28 }
29}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.literal.Literal;
6import tools.refinery.store.query.substitution.Substitution;
7import tools.refinery.store.query.valuation.Valuation;
8
9import java.util.Objects;
10
11public final class DataVariable<T> extends AnyDataVariable implements Term<T> {
12 private final Class<T> type;
13
14 DataVariable(String name, Class<T> type) {
15 super(name);
16 this.type = type;
17 }
18
19 @Override
20 public DataSort<T> getSort() {
21 return new DataSort<>(getType());
22 }
23
24 @Override
25 public Class<T> getType() {
26 return type;
27 }
28
29 @Override
30 public DataVariable<T> renew(@Nullable String name) {
31 return new DataVariable<>(name, type);
32 }
33
34 @Override
35 public DataVariable<T> renew() {
36 return renew(getExplicitName());
37 }
38
39 @Override
40 public NodeVariable asNodeVariable() {
41 throw new IllegalStateException("%s is a data variable".formatted(this));
42 }
43
44 @Override
45 public <U> DataVariable<U> asDataVariable(Class<U> newType) {
46 if (!getType().equals(newType)) {
47 throw new IllegalStateException("%s is not of type %s but of type %s".formatted(this, newType.getName(),
48 getType().getName()));
49 }
50 @SuppressWarnings("unchecked")
51 var result = (DataVariable<U>) this;
52 return result;
53 }
54
55 @Override
56 public T evaluate(Valuation valuation) {
57 return valuation.getValue(this);
58 }
59
60 @Override
61 public Term<T> substitute(Substitution substitution) {
62 return substitution.getTypeSafeSubstitute(this);
63 }
64
65 @Override
66 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
67 return other instanceof DataVariable<?> dataVariable && helper.variableEqual(this, dataVariable);
68 }
69
70 public Literal assign(AssignedValue<T> value) {
71 return value.toLiteral(this);
72 }
73
74 @Override
75 public boolean equals(Object o) {
76 if (this == o) return true;
77 if (o == null || getClass() != o.getClass()) return false;
78 if (!super.equals(o)) return false;
79 DataVariable<?> that = (DataVariable<?>) o;
80 return type.equals(that.type);
81 }
82
83 @Override
84 public int hashCode() {
85 return Objects.hash(super.hashCode(), type);
86 }
87}
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 @@
1package tools.refinery.store.query.term;
2
3import java.util.Comparator;
4import java.util.Objects;
5import java.util.SortedMap;
6import java.util.TreeMap;
7
8public class ExtremeValueAggregator<T> implements StatefulAggregator<T, T> {
9 private final Class<T> type;
10 private final T emptyResult;
11 private final Comparator<T> comparator;
12
13 public ExtremeValueAggregator(Class<T> type, T emptyResult) {
14 this(type, emptyResult, null);
15 }
16
17 public ExtremeValueAggregator(Class<T> type, T emptyResult, Comparator<T> comparator) {
18 this.type = type;
19 this.emptyResult = emptyResult;
20 this.comparator = comparator;
21 }
22
23 @Override
24 public Class<T> getResultType() {
25 return getInputType();
26 }
27
28 @Override
29 public Class<T> getInputType() {
30 return type;
31 }
32
33 @Override
34 public StatefulAggregate<T, T> createEmptyAggregate() {
35 return new Aggregate();
36 }
37
38 @Override
39 public T getEmptyResult() {
40 return emptyResult;
41 }
42
43 @Override
44 public boolean equals(Object o) {
45 if (this == o) return true;
46 if (o == null || getClass() != o.getClass()) return false;
47 ExtremeValueAggregator<?> that = (ExtremeValueAggregator<?>) o;
48 return type.equals(that.type) && Objects.equals(emptyResult, that.emptyResult) && Objects.equals(comparator,
49 that.comparator);
50 }
51
52 @Override
53 public int hashCode() {
54 return Objects.hash(type, emptyResult, comparator);
55 }
56
57 private class Aggregate implements StatefulAggregate<T, T> {
58 private final SortedMap<T, Integer> values;
59
60 private Aggregate() {
61 values = new TreeMap<>(comparator);
62 }
63
64 private Aggregate(Aggregate other) {
65 values = new TreeMap<>(other.values);
66 }
67
68 @Override
69 public void add(T value) {
70 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
71 }
72
73 @Override
74 public void remove(T value) {
75 values.compute(value, (theValue, currentCount) -> {
76 if (currentCount == null || currentCount <= 0) {
77 throw new IllegalStateException("Invalid count %d for value %s".formatted(currentCount, theValue));
78 }
79 return currentCount.equals(1) ? null : currentCount - 1;
80 });
81 }
82
83 @Override
84 public T getResult() {
85 return isEmpty() ? emptyResult : values.firstKey();
86 }
87
88 @Override
89 public boolean isEmpty() {
90 return values.isEmpty();
91 }
92
93 @Override
94 public StatefulAggregate<T, T> deepCopy() {
95 return new Aggregate(this);
96 }
97
98 @Override
99 public boolean contains(T value) {
100 return StatefulAggregate.super.contains(value);
101 }
102 }
103}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public final class NodeSort implements Sort {
6 public static final NodeSort INSTANCE = new NodeSort();
7
8 private NodeSort() {
9 }
10
11 @Override
12 public boolean isInstance(Variable variable) {
13 return variable instanceof NodeVariable;
14 }
15
16 @Override
17 public NodeVariable newInstance(@Nullable String name) {
18 return new NodeVariable(name);
19 }
20
21 @Override
22 public NodeVariable newInstance() {
23 return newInstance(null);
24 }
25
26 @Override
27 public String toString() {
28 return "<node>";
29 }
30}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.literal.ConstantLiteral;
5import tools.refinery.store.query.literal.EquivalenceLiteral;
6
7public final class NodeVariable extends Variable {
8 NodeVariable(@Nullable String name) {
9 super(name);
10 }
11
12 @Override
13 public NodeSort getSort() {
14 return NodeSort.INSTANCE;
15 }
16
17 @Override
18 public NodeVariable renew(@Nullable String name) {
19 return Variable.of(name);
20 }
21
22 @Override
23 public NodeVariable renew() {
24 return renew(getExplicitName());
25 }
26
27 @Override
28 public NodeVariable asNodeVariable() {
29 return this;
30 }
31
32 @Override
33 public <T> DataVariable<T> asDataVariable(Class<T> type) {
34 throw new IllegalStateException("%s is a node variable".formatted(this));
35 }
36
37 public ConstantLiteral isConstant(int value) {
38 return new ConstantLiteral(this, value);
39 }
40
41 public EquivalenceLiteral isEquivalent(NodeVariable other) {
42 return new EquivalenceLiteral(true, this, other);
43 }
44
45 public EquivalenceLiteral notEquivalent(NodeVariable other) {
46 return new EquivalenceLiteral(false, this, other);
47 }
48}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.substitution.Substitutions;
6import tools.refinery.store.query.valuation.Valuation;
7
8import java.util.Objects;
9import java.util.Set;
10import java.util.function.Function;
11import java.util.stream.Collectors;
12
13public final class OpaqueTerm<T> implements Term<T> {
14 private final Class<T> type;
15 private final Function<? super Valuation, ? extends T> evaluator;
16 private final Set<AnyDataVariable> variables;
17 private final Substitution substitution;
18
19 public OpaqueTerm(Class<T> type, Function<? super Valuation, ? extends T> evaluator,
20 Set<? extends AnyDataVariable> variables) {
21 this(type, evaluator, variables, null);
22 }
23
24 private OpaqueTerm(Class<T> type, Function<? super Valuation, ? extends T> evaluator,
25 Set<? extends AnyDataVariable> variables, Substitution substitution) {
26 this.type = type;
27 this.evaluator = evaluator;
28 this.variables = Set.copyOf(variables);
29 this.substitution = substitution;
30 }
31
32 @Override
33 public Class<T> getType() {
34 return type;
35 }
36
37 @Override
38 public Set<AnyDataVariable> getInputVariables() {
39 return variables;
40 }
41
42 @Override
43 public T evaluate(Valuation valuation) {
44 return evaluator.apply(valuation.substitute(substitution));
45 }
46
47 @Override
48 public Term<T> substitute(Substitution newSubstitution) {
49 var substitutedVariables = variables.stream()
50 .map(newSubstitution::getTypeSafeSubstitute)
51 .collect(Collectors.toUnmodifiableSet());
52 return new OpaqueTerm<>(type, evaluator, substitutedVariables,
53 Substitutions.compose(substitution, newSubstitution));
54 }
55
56 @Override
57 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
58 // Cannot inspect the opaque evaluator for deep equality.
59 return equals(other);
60 }
61
62 @Override
63 public String toString() {
64 return "<opaque>";
65 }
66
67 @Override
68 public boolean equals(Object o) {
69 if (this == o) return true;
70 if (o == null || getClass() != o.getClass()) return false;
71 OpaqueTerm<?> that = (OpaqueTerm<?>) o;
72 return type.equals(that.type) && evaluator.equals(that.evaluator) && Objects.equals(substitution,
73 that.substitution);
74 }
75
76 @Override
77 public int hashCode() {
78 return Objects.hash(type, evaluator, substitution);
79 }
80}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4
5public sealed interface Sort permits DataSort, NodeSort {
6 boolean isInstance(Variable variable);
7
8 Variable newInstance(@Nullable String name);
9
10 Variable newInstance();
11}
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 @@
1package tools.refinery.store.query.term;
2
3public interface StatefulAggregate<R, T> {
4 void add(T value);
5
6 void remove(T value);
7
8 R getResult();
9
10 boolean isEmpty();
11
12 StatefulAggregate<R, T> deepCopy();
13
14 default boolean contains(T value) {
15 throw new UnsupportedOperationException();
16 }
17}
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 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface StatefulAggregator<R, T> extends Aggregator<R, T> {
6 StatefulAggregate<R, T> createEmptyAggregate();
7
8 @Override
9 default R aggregateStream(Stream<T> stream) {
10 var accumulator = createEmptyAggregate();
11 var iterator = stream.iterator();
12 while (iterator.hasNext()) {
13 var value = iterator.next();
14 accumulator.add(value);
15 }
16 return accumulator.getResult();
17 }
18
19 @Override
20 default R getEmptyResult() {
21 return createEmptyAggregate().getResult();
22 }
23}
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 @@
1package tools.refinery.store.query.term;
2
3import java.util.stream.Stream;
4
5public interface StatelessAggregator<R, T> extends Aggregator<R, T> {
6 R add(R current, T value);
7
8 R remove(R current, T value);
9
10 @Override
11 default R aggregateStream(Stream<T> stream) {
12 var accumulator = getEmptyResult();
13 var iterator = stream.iterator();
14 while (iterator.hasNext()) {
15 var value = iterator.next();
16 accumulator = add(accumulator, value);
17 }
18 return accumulator;
19 }
20}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.literal.AssignLiteral;
4import tools.refinery.store.query.literal.Literal;
5import tools.refinery.store.query.substitution.Substitution;
6import tools.refinery.store.query.valuation.Valuation;
7
8public non-sealed interface Term<T> extends AnyTerm, AssignedValue<T> {
9 @Override
10 Class<T> getType();
11
12 T evaluate(Valuation valuation);
13
14 @Override
15 Term<T> substitute(Substitution substitution);
16
17 @Override
18 default Literal toLiteral(DataVariable<T> targetVariable) {
19 return new AssignLiteral<>(targetVariable, this);
20 }
21}
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 @@
1package tools.refinery.store.query.term;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.valuation.Valuation;
6
7import java.util.Objects;
8import java.util.Set;
9
10public abstract class UnaryTerm<R, T> implements Term<R> {
11 private final Term<T> body;
12
13 protected UnaryTerm(Term<T> body) {
14 if (!body.getType().equals(getBodyType())) {
15 throw new IllegalArgumentException("Expected body %s to be of type %s, got %s instead".formatted(body,
16 getBodyType().getName(), body.getType().getName()));
17 }
18 this.body = body;
19 }
20
21 public abstract Class<T> getBodyType();
22
23 public Term<T> getBody() {
24 return body;
25 }
26
27 @Override
28 public R evaluate(Valuation valuation) {
29 var bodyValue = body.evaluate(valuation);
30 return bodyValue == null ? null : doEvaluate(bodyValue);
31 }
32
33 protected abstract R doEvaluate(T bodyValue);
34
35 @Override
36 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
37 if (getClass() != other.getClass()) {
38 return false;
39 }
40 var otherUnaryTerm = (UnaryTerm<?, ?>) other;
41 return body.equalsWithSubstitution(helper, otherUnaryTerm.body);
42 }
43
44 @Override
45 public Term<R> substitute(Substitution substitution) {
46 return doSubstitute(substitution, body.substitute(substitution));
47 }
48
49 protected abstract Term<R> doSubstitute(Substitution substitution, Term<T> substitutedBody);
50
51 @Override
52 public Set<AnyDataVariable> getInputVariables() {
53 return body.getInputVariables();
54 }
55
56 @Override
57 public boolean equals(Object o) {
58 if (this == o) return true;
59 if (o == null || getClass() != o.getClass()) return false;
60 UnaryTerm<?, ?> unaryTerm = (UnaryTerm<?, ?>) o;
61 return body.equals(unaryTerm.body);
62 }
63
64 @Override
65 public int hashCode() {
66 return Objects.hash(getClass(), body);
67 }
68}
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 @@
1package tools.refinery.store.query.term;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.dnf.DnfUtils;
5
6import java.util.Objects;
7
8public abstract sealed class Variable permits AnyDataVariable, NodeVariable {
9 private final String explicitName;
10 private final String uniqueName;
11
12 protected Variable(String name) {
13 this.explicitName = name;
14 uniqueName = DnfUtils.generateUniqueName(name);
15 }
16
17 public abstract Sort getSort();
18
19 public String getName() {
20 return explicitName == null ? uniqueName : explicitName;
21 }
22
23 protected String getExplicitName() {
24 return explicitName;
25 }
26
27 public boolean isExplicitlyNamed() {
28 return explicitName != null;
29 }
30
31 public String getUniqueName() {
32 return uniqueName;
33 }
34
35 public abstract Variable renew(@Nullable String name);
36
37 public abstract Variable renew();
38
39 public abstract NodeVariable asNodeVariable();
40
41 public abstract <T> DataVariable<T> asDataVariable(Class<T> type);
42
43 @Override
44 public String toString() {
45 return getName();
46 }
47
48 @Override
49 public boolean equals(Object o) {
50 if (this == o) return true;
51 if (o == null || getClass() != o.getClass()) return false;
52 Variable variable = (Variable) o;
53 return Objects.equals(uniqueName, variable.uniqueName);
54 }
55
56 @Override
57 public int hashCode() {
58 return Objects.hash(uniqueName);
59 }
60
61 public static NodeVariable of(@Nullable String name) {
62 return new NodeVariable(name);
63 }
64
65 public static NodeVariable of() {
66 return of((String) null);
67 }
68
69 public static <T> DataVariable<T> of(@Nullable String name, Class<T> type) {
70 return new DataVariable<>(name, type);
71 }
72
73 public static <T> DataVariable<T> of(Class<T> type) {
74 return of(null, type);
75 }
76}
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 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.term.ConstantTerm;
4
5public final class BoolConstantTerm {
6 public static final ConstantTerm<Boolean> TRUE = new ConstantTerm<>(Boolean.class, true);
7 public static final ConstantTerm<Boolean> FALSE = new ConstantTerm<>(Boolean.class, false);
8
9 private BoolConstantTerm() {
10 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
11 }
12
13 public static ConstantTerm<Boolean> valueOf(boolean boolValue) {
14 return boolValue ? TRUE : FALSE;
15 }
16}
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 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.equality.LiteralEqualityHelper;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.*;
6
7import java.util.Objects;
8
9public class BoolLogicBinaryTerm extends BinaryTerm<Boolean, Boolean, Boolean> {
10 private final LogicBinaryOperator operator;
11
12 protected BoolLogicBinaryTerm(LogicBinaryOperator operator, Term<Boolean> left, Term<Boolean> right) {
13 super(left, right);
14 this.operator = operator;
15 }
16
17 @Override
18 public Class<Boolean> getType() {
19 return Boolean.class;
20 }
21
22 @Override
23 public Class<Boolean> getLeftType() {
24 return getType();
25 }
26
27 @Override
28 public Class<Boolean> getRightType() {
29 return getType();
30 }
31
32 public LogicBinaryOperator getOperator() {
33 return operator;
34 }
35
36 @Override
37 public boolean equalsWithSubstitution(LiteralEqualityHelper helper, AnyTerm other) {
38 if (!super.equalsWithSubstitution(helper, other)) {
39 return false;
40 }
41 var otherBoolLogicBinaryTerm = (BoolLogicBinaryTerm) other;
42 return operator == otherBoolLogicBinaryTerm.operator;
43 }
44
45 @Override
46 public Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedLeft,
47 Term<Boolean> substitutedRight) {
48 return new BoolLogicBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
49 }
50
51 @Override
52 protected Boolean doEvaluate(Boolean leftValue, Boolean rightValue) {
53 return switch (getOperator()) {
54 case AND -> leftValue && rightValue;
55 case OR -> leftValue || rightValue;
56 case XOR -> leftValue ^ rightValue;
57 };
58 }
59
60 @Override
61 public String toString() {
62 return operator.formatString(getLeft().toString(), getRight().toString());
63 }
64
65 @Override
66 public boolean equals(Object o) {
67 if (this == o) return true;
68 if (o == null || getClass() != o.getClass()) return false;
69 if (!super.equals(o)) return false;
70 BoolLogicBinaryTerm that = (BoolLogicBinaryTerm) o;
71 return operator == that.operator;
72 }
73
74 @Override
75 public int hashCode() {
76 return Objects.hash(super.hashCode(), operator);
77 }
78}
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 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class BoolNotTerm extends UnaryTerm<Boolean, Boolean> {
8 protected BoolNotTerm(Term<Boolean> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Boolean> getType() {
14 return Boolean.class;
15 }
16
17 @Override
18 public Class<Boolean> getBodyType() {
19 return getType();
20 }
21
22 @Override
23 protected Term<Boolean> doSubstitute(Substitution substitution, Term<Boolean> substitutedBody) {
24 return new BoolNotTerm(substitutedBody);
25 }
26
27 @Override
28 protected Boolean doEvaluate(Boolean bodyValue) {
29 return !bodyValue;
30 }
31
32 @Override
33 public String toString() {
34 return "!(%s)".formatted(getBody());
35 }
36}
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 @@
1package tools.refinery.store.query.term.bool;
2
3import tools.refinery.store.query.term.ConstantTerm;
4import tools.refinery.store.query.term.Term;
5
6public final class BoolTerms {
7 private BoolTerms() {
8 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
9 }
10
11 public static ConstantTerm<Boolean> constant(boolean value) {
12 return BoolConstantTerm.valueOf(value);
13 }
14
15 public static BoolNotTerm not(Term<Boolean> body) {
16 return new BoolNotTerm(body);
17 }
18
19 public static BoolLogicBinaryTerm and(Term<Boolean> left, Term<Boolean> right) {
20 return new BoolLogicBinaryTerm(LogicBinaryOperator.AND, left, right);
21 }
22
23 public static BoolLogicBinaryTerm or(Term<Boolean> left, Term<Boolean> right) {
24 return new BoolLogicBinaryTerm(LogicBinaryOperator.OR, left, right);
25 }
26
27 public static BoolLogicBinaryTerm xor(Term<Boolean> left, Term<Boolean> right) {
28 return new BoolLogicBinaryTerm(LogicBinaryOperator.XOR, left, right);
29 }
30}
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 @@
1package tools.refinery.store.query.term.bool;
2
3public enum LogicBinaryOperator {
4 AND("&&"),
5 OR("||"),
6 XOR("^^");
7
8 private final String text;
9
10 LogicBinaryOperator(String text) {
11 this.text = text;
12 }
13
14 public String formatString(String left, String right) {
15 return "(%s) %s (%s)".formatted(left, text, right);
16 }
17}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticBinaryOperator;
5import tools.refinery.store.query.term.ArithmeticBinaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class IntArithmeticBinaryTerm extends ArithmeticBinaryTerm<Integer> {
9 public IntArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<Integer> left, Term<Integer> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Integer> getType() {
15 return Integer.class;
16 }
17
18 @Override
19 public Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
20 Term<Integer> substitutedRight) {
21 return new IntArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Integer doEvaluate(Integer leftValue, Integer rightValue) {
26 return switch (getOperator()) {
27 case ADD -> leftValue + rightValue;
28 case SUB -> leftValue - rightValue;
29 case MUL -> leftValue * rightValue;
30 case DIV -> rightValue == 0 ? null : leftValue / rightValue;
31 case POW -> rightValue < 0 ? null : power(leftValue, rightValue);
32 case MIN -> Math.min(leftValue, rightValue);
33 case MAX -> Math.max(leftValue, rightValue);
34 };
35 }
36
37 private static int power(int base, int exponent) {
38 int accum = 1;
39 while (exponent > 0) {
40 if (exponent % 2 == 1) {
41 accum = accum * base;
42 }
43 base = base * base;
44 exponent = exponent / 2;
45 }
46 return accum;
47 }
48}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.ArithmeticUnaryOperator;
6import tools.refinery.store.query.term.ArithmeticUnaryTerm;
7
8public class IntArithmeticUnaryTerm extends ArithmeticUnaryTerm<Integer> {
9 public IntArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term<Integer> body) {
10 super(operation, body);
11 }
12
13 @Override
14 public Class<Integer> getType() {
15 return Integer.class;
16 }
17
18 @Override
19 protected Term<Integer> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
20 return new IntArithmeticUnaryTerm(getOperator(), substitutedBody);
21 }
22
23 @Override
24 protected Integer doEvaluate(Integer bodyValue) {
25 return switch(getOperator()) {
26 case PLUS -> bodyValue;
27 case MINUS -> -bodyValue;
28 };
29 }
30}
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 @@
1package tools.refinery.store.query.term.int_;
2import tools.refinery.store.query.substitution.Substitution;
3import tools.refinery.store.query.term.ComparisonOperator;
4import tools.refinery.store.query.term.ComparisonTerm;
5import tools.refinery.store.query.term.Term;
6
7public class IntComparisonTerm extends ComparisonTerm<Integer> {
8 public IntComparisonTerm(ComparisonOperator operator, Term<Integer> left, Term<Integer> right) {
9 super(operator, left, right);
10 }
11
12 @Override
13 public Class<Integer> getOperandType() {
14 return Integer.class;
15 }
16
17 @Override
18 public Term<Boolean> doSubstitute(Substitution substitution, Term<Integer> substitutedLeft,
19 Term<Integer> substitutedRight) {
20 return new IntComparisonTerm(getOperator(), substitutedLeft, substitutedRight);
21 }
22
23 @Override
24 protected Boolean doEvaluate(Integer leftValue, Integer rightValue) {
25 return switch (getOperator()) {
26 case EQ -> leftValue.equals(rightValue);
27 case NOT_EQ -> !leftValue.equals(rightValue);
28 case LESS -> leftValue < rightValue;
29 case LESS_EQ -> leftValue <= rightValue;
30 case GREATER -> leftValue > rightValue;
31 case GREATER_EQ -> leftValue >= rightValue;
32 };
33 }
34}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.ExtremeValueAggregator;
4
5import java.util.Comparator;
6
7public final class IntExtremeValueAggregator {
8 public static final ExtremeValueAggregator<Integer> MINIMUM = new ExtremeValueAggregator<>(Integer.class,
9 Integer.MAX_VALUE);
10
11 public static final ExtremeValueAggregator<Integer> MAXIMUM = new ExtremeValueAggregator<>(Integer.class,
12 Integer.MIN_VALUE, Comparator.reverseOrder());
13
14 private IntExtremeValueAggregator() {
15 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
16 }
17}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.StatelessAggregator;
4
5public final class IntSumAggregator implements StatelessAggregator<Integer, Integer> {
6 public static final IntSumAggregator INSTANCE = new IntSumAggregator();
7
8 private IntSumAggregator() {
9 }
10
11 @Override
12 public Class<Integer> getResultType() {
13 return Integer.class;
14 }
15
16 @Override
17 public Class<Integer> getInputType() {
18 return Integer.class;
19 }
20
21 @Override
22 public Integer getEmptyResult() {
23 return 0;
24 }
25
26 @Override
27 public Integer add(Integer current, Integer value) {
28 return current + value;
29 }
30
31 @Override
32 public Integer remove(Integer current, Integer value) {
33 return current - value;
34 }
35}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.term.*;
4
5public final class IntTerms {
6 public static final Aggregator<Integer, Integer> INT_SUM = IntSumAggregator.INSTANCE;
7 public static final Aggregator<Integer, Integer> INT_MIN = IntExtremeValueAggregator.MINIMUM;
8 public static final Aggregator<Integer, Integer> INT_MAX = IntExtremeValueAggregator.MAXIMUM;
9
10 private IntTerms() {
11 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
12 }
13
14 public static ConstantTerm<Integer> constant(int value) {
15 return new ConstantTerm<>(Integer.class, value);
16 }
17
18 public static IntArithmeticUnaryTerm plus(Term<Integer> body) {
19 return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body);
20 }
21
22 public static IntArithmeticUnaryTerm minus(Term<Integer> body) {
23 return new IntArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body);
24 }
25
26 public static IntArithmeticBinaryTerm add(Term<Integer> left, Term<Integer> right) {
27 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right);
28 }
29
30 public static IntArithmeticBinaryTerm sub(Term<Integer> left, Term<Integer> right) {
31 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right);
32 }
33
34 public static IntArithmeticBinaryTerm mul(Term<Integer> left, Term<Integer> right) {
35 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right);
36 }
37
38 public static IntArithmeticBinaryTerm div(Term<Integer> left, Term<Integer> right) {
39 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right);
40 }
41
42 public static IntArithmeticBinaryTerm pow(Term<Integer> left, Term<Integer> right) {
43 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right);
44 }
45
46 public static IntArithmeticBinaryTerm min(Term<Integer> left, Term<Integer> right) {
47 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right);
48 }
49
50 public static IntArithmeticBinaryTerm max(Term<Integer> left, Term<Integer> right) {
51 return new IntArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right);
52 }
53
54 public static IntComparisonTerm eq(Term<Integer> left, Term<Integer> right) {
55 return new IntComparisonTerm(ComparisonOperator.EQ, left, right);
56 }
57
58 public static IntComparisonTerm notEq(Term<Integer> left, Term<Integer> right) {
59 return new IntComparisonTerm(ComparisonOperator.NOT_EQ, left, right);
60 }
61
62 public static IntComparisonTerm less(Term<Integer> left, Term<Integer> right) {
63 return new IntComparisonTerm(ComparisonOperator.LESS, left, right);
64 }
65
66 public static IntComparisonTerm lessEq(Term<Integer> left, Term<Integer> right) {
67 return new IntComparisonTerm(ComparisonOperator.LESS_EQ, left, right);
68 }
69
70 public static IntComparisonTerm greater(Term<Integer> left, Term<Integer> right) {
71 return new IntComparisonTerm(ComparisonOperator.GREATER, left, right);
72 }
73
74 public static IntComparisonTerm greaterEq(Term<Integer> left, Term<Integer> right) {
75 return new IntComparisonTerm(ComparisonOperator.GREATER_EQ, left, right);
76 }
77
78 public static RealToIntTerm asInt(Term<Double> body) {
79 return new RealToIntTerm(body);
80 }
81}
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 @@
1package tools.refinery.store.query.term.int_;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class RealToIntTerm extends UnaryTerm<Integer, Double> {
8 protected RealToIntTerm(Term<Double> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Integer> getType() {
14 return Integer.class;
15 }
16
17 @Override
18 public Class<Double> getBodyType() {
19 return Double.class;
20 }
21
22 @Override
23 protected Integer doEvaluate(Double bodyValue) {
24 return bodyValue.intValue();
25 }
26
27 @Override
28 protected Term<Integer> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
29 return new RealToIntTerm(substitutedBody);
30 }
31
32 @Override
33 public String toString() {
34 return "(%s) as int".formatted(getBody());
35 }
36}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.Term;
5import tools.refinery.store.query.term.UnaryTerm;
6
7public class IntToRealTerm extends UnaryTerm<Double, Integer> {
8 protected IntToRealTerm(Term<Integer> body) {
9 super(body);
10 }
11
12 @Override
13 public Class<Double> getType() {
14 return Double.class;
15 }
16
17 @Override
18 public Class<Integer> getBodyType() {
19 return Integer.class;
20 }
21
22 @Override
23 protected Term<Double> doSubstitute(Substitution substitution, Term<Integer> substitutedBody) {
24 return new IntToRealTerm(substitutedBody);
25 }
26
27 @Override
28 protected Double doEvaluate(Integer bodyValue) {
29 return bodyValue.doubleValue();
30 }
31
32 @Override
33 public String toString() {
34 return "(%s) as real".formatted(getBody());
35 }
36}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticBinaryOperator;
5import tools.refinery.store.query.term.ArithmeticBinaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealArithmeticBinaryTerm extends ArithmeticBinaryTerm<Double> {
9 public RealArithmeticBinaryTerm(ArithmeticBinaryOperator operator, Term<Double> left, Term<Double> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Double> getType() {
15 return Double.class;
16 }
17
18 @Override
19 public Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
20 Term<Double> substitutedRight) {
21 return new RealArithmeticBinaryTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Double doEvaluate(Double leftValue, Double rightValue) {
26 return switch (getOperator()) {
27 case ADD -> leftValue + rightValue;
28 case SUB -> leftValue - rightValue;
29 case MUL -> leftValue * rightValue;
30 case DIV -> leftValue / rightValue;
31 case POW -> Math.pow(leftValue, rightValue);
32 case MIN -> Math.min(leftValue, rightValue);
33 case MAX -> Math.max(leftValue, rightValue);
34 };
35 }
36}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ArithmeticUnaryOperator;
5import tools.refinery.store.query.term.ArithmeticUnaryTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealArithmeticUnaryTerm extends ArithmeticUnaryTerm<Double> {
9 public RealArithmeticUnaryTerm(ArithmeticUnaryOperator operation, Term<Double> body) {
10 super(operation, body);
11 }
12
13 @Override
14 public Class<Double> getType() {
15 return Double.class;
16 }
17
18 @Override
19 protected Term<Double> doSubstitute(Substitution substitution, Term<Double> substitutedBody) {
20 return new RealArithmeticUnaryTerm(getOperator(), substitutedBody);
21 }
22
23 @Override
24 protected Double doEvaluate(Double bodyValue) {
25 return switch(getOperator()) {
26 case PLUS -> bodyValue;
27 case MINUS -> -bodyValue;
28 };
29 }
30}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.ComparisonOperator;
5import tools.refinery.store.query.term.ComparisonTerm;
6import tools.refinery.store.query.term.Term;
7
8public class RealComparisonTerm extends ComparisonTerm<Double> {
9 public RealComparisonTerm(ComparisonOperator operator, Term<Double> left, Term<Double> right) {
10 super(operator, left, right);
11 }
12
13 @Override
14 public Class<Double> getOperandType() {
15 return Double.class;
16 }
17
18 @Override
19 public Term<Boolean> doSubstitute(Substitution substitution, Term<Double> substitutedLeft,
20 Term<Double> substitutedRight) {
21 return new RealComparisonTerm(getOperator(), substitutedLeft, substitutedRight);
22 }
23
24 @Override
25 protected Boolean doEvaluate(Double leftValue, Double rightValue) {
26 return switch (getOperator()) {
27 case EQ -> leftValue.equals(rightValue);
28 case NOT_EQ -> !leftValue.equals(rightValue);
29 case LESS -> leftValue < rightValue;
30 case LESS_EQ -> leftValue <= rightValue;
31 case GREATER -> leftValue > rightValue;
32 case GREATER_EQ -> leftValue >= rightValue;
33 };
34 }
35}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.ExtremeValueAggregator;
4
5import java.util.Comparator;
6
7public final class RealExtremeValueAggregator {
8 public static final ExtremeValueAggregator<Double> MINIMUM = new ExtremeValueAggregator<>(Double.class,
9 Double.POSITIVE_INFINITY);
10
11 public static final ExtremeValueAggregator<Double> MAXIMUM = new ExtremeValueAggregator<>(Double.class,
12 Double.NEGATIVE_INFINITY, Comparator.reverseOrder());
13
14 private RealExtremeValueAggregator() {
15 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
16 }
17}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.StatefulAggregate;
4import tools.refinery.store.query.term.StatefulAggregator;
5
6import java.util.Map;
7import java.util.TreeMap;
8
9public final class RealSumAggregator implements StatefulAggregator<Double, Double> {
10 public static final RealSumAggregator INSTANCE = new RealSumAggregator();
11
12 private RealSumAggregator() {
13 }
14
15 @Override
16 public Class<Double> getResultType() {
17 return null;
18 }
19
20 @Override
21 public Class<Double> getInputType() {
22 return null;
23 }
24
25 @Override
26 public StatefulAggregate<Double, Double> createEmptyAggregate() {
27 return new Aggregate();
28 }
29
30 @Override
31 public Double getEmptyResult() {
32 return 0d;
33 }
34
35 private static class Aggregate implements StatefulAggregate<Double, Double> {
36 private final Map<Double, Integer> values;
37
38 public Aggregate() {
39 values = new TreeMap<>();
40 }
41
42 private Aggregate(Aggregate other) {
43 values = new TreeMap<>(other.values);
44 }
45
46 @Override
47 public void add(Double value) {
48 values.compute(value, (ignoredValue, currentCount) -> currentCount == null ? 1 : currentCount + 1);
49 }
50
51 @Override
52 public void remove(Double value) {
53 values.compute(value, (theValue, currentCount) -> {
54 if (currentCount == null || currentCount <= 0) {
55 throw new IllegalStateException("Invalid count %d for value %f".formatted(currentCount, theValue));
56 }
57 return currentCount.equals(1) ? null : currentCount - 1;
58 });
59 }
60
61 @Override
62 public Double getResult() {
63 return values.entrySet()
64 .stream()
65 .mapToDouble(entry -> entry.getKey() * entry.getValue())
66 .reduce(Double::sum)
67 .orElse(0d);
68 }
69
70 @Override
71 public boolean isEmpty() {
72 return values.isEmpty();
73 }
74
75 @Override
76 public StatefulAggregate<Double, Double> deepCopy() {
77 return new Aggregate(this);
78 }
79
80 @Override
81 public boolean contains(Double value) {
82 return values.containsKey(value);
83 }
84 }
85}
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 @@
1package tools.refinery.store.query.term.real;
2
3import tools.refinery.store.query.term.*;
4
5public final class RealTerms {
6 public static final Aggregator<Double, Double> REAL_SUM = RealSumAggregator.INSTANCE;
7 public static final Aggregator<Double, Double> REAL_MIN = RealExtremeValueAggregator.MINIMUM;
8 public static final Aggregator<Double, Double> REAL_MAX = RealExtremeValueAggregator.MAXIMUM;
9
10 private RealTerms() {
11 throw new IllegalArgumentException("This is a static utility class and should not be instantiated directly");
12 }
13
14 public static ConstantTerm<Double> constant(double value) {
15 return new ConstantTerm<>(Double.class, value);
16 }
17
18 public static RealArithmeticUnaryTerm plus(Term<Double> body) {
19 return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.PLUS, body);
20 }
21
22 public static RealArithmeticUnaryTerm minus(Term<Double> body) {
23 return new RealArithmeticUnaryTerm(ArithmeticUnaryOperator.MINUS, body);
24 }
25
26 public static RealArithmeticBinaryTerm add(Term<Double> left, Term<Double> right) {
27 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.ADD, left, right);
28 }
29
30 public static RealArithmeticBinaryTerm sub(Term<Double> left, Term<Double> right) {
31 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.SUB, left, right);
32 }
33
34 public static RealArithmeticBinaryTerm mul(Term<Double> left, Term<Double> right) {
35 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MUL, left, right);
36 }
37
38 public static RealArithmeticBinaryTerm div(Term<Double> left, Term<Double> right) {
39 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.DIV, left, right);
40 }
41
42 public static RealArithmeticBinaryTerm pow(Term<Double> left, Term<Double> right) {
43 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.POW, left, right);
44 }
45
46 public static RealArithmeticBinaryTerm min(Term<Double> left, Term<Double> right) {
47 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MIN, left, right);
48 }
49
50 public static RealArithmeticBinaryTerm max(Term<Double> left, Term<Double> right) {
51 return new RealArithmeticBinaryTerm(ArithmeticBinaryOperator.MAX, left, right);
52 }
53
54 public static RealComparisonTerm eq(Term<Double> left, Term<Double> right) {
55 return new RealComparisonTerm(ComparisonOperator.EQ, left, right);
56 }
57
58 public static RealComparisonTerm notEq(Term<Double> left, Term<Double> right) {
59 return new RealComparisonTerm(ComparisonOperator.NOT_EQ, left, right);
60 }
61
62 public static RealComparisonTerm less(Term<Double> left, Term<Double> right) {
63 return new RealComparisonTerm(ComparisonOperator.LESS, left, right);
64 }
65
66 public static RealComparisonTerm lessEq(Term<Double> left, Term<Double> right) {
67 return new RealComparisonTerm(ComparisonOperator.LESS_EQ, left, right);
68 }
69
70 public static RealComparisonTerm greater(Term<Double> left, Term<Double> right) {
71 return new RealComparisonTerm(ComparisonOperator.GREATER, left, right);
72 }
73
74 public static RealComparisonTerm greaterEq(Term<Double> left, Term<Double> right) {
75 return new RealComparisonTerm(ComparisonOperator.GREATER_EQ, left, right);
76 }
77
78 public static IntToRealTerm asReal(Term<Integer> body) {
79 return new IntToRealTerm(body);
80 }
81}
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 @@
1package tools.refinery.store.query.valuation;
2
3import tools.refinery.store.query.term.AnyDataVariable;
4import tools.refinery.store.query.term.DataVariable;
5
6import java.util.Set;
7
8public record RestrictedValuation(Valuation valuation, Set<AnyDataVariable> allowedVariables) implements Valuation {
9 @Override
10 public <T> T getValue(DataVariable<T> variable) {
11 if (!allowedVariables.contains(variable)) {
12 throw new IllegalArgumentException("Variable %s is not in scope".formatted(variable));
13 }
14 return valuation.getValue(variable);
15 }
16}
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 @@
1package tools.refinery.store.query.valuation;
2
3import tools.refinery.store.query.substitution.Substitution;
4import tools.refinery.store.query.term.DataVariable;
5
6public record SubstitutedValuation(Valuation originalValuation, Substitution substitution) implements Valuation {
7 @Override
8 public <T> T getValue(DataVariable<T> variable) {
9 return originalValuation.getValue(substitution.getTypeSafeSubstitute(variable));
10 }
11}
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 @@
1package tools.refinery.store.query.valuation;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.substitution.Substitution;
5import tools.refinery.store.query.term.AnyDataVariable;
6import tools.refinery.store.query.term.DataVariable;
7
8import java.util.Set;
9
10public interface Valuation {
11 <T> T getValue(DataVariable<T> variable);
12
13 default Valuation substitute(@Nullable Substitution substitution) {
14 if (substitution == null) {
15 return this;
16 }
17 return new SubstitutedValuation(this, substitution);
18 }
19
20 default Valuation restrict(Set<? extends AnyDataVariable> allowedVariables) {
21 return new RestrictedValuation(this, Set.copyOf(allowedVariables));
22 }
23}
diff --git a/subprojects/store/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 328cde3a..6ae410f2 100644
--- a/subprojects/store/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,15 +1,17 @@
1package tools.refinery.store.query.view; 1package tools.refinery.store.query.view;
2 2
3import tools.refinery.store.model.Model; 3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency; 4import tools.refinery.store.query.dnf.FunctionalDependency;
5import tools.refinery.store.representation.AnySymbol; 5import tools.refinery.store.representation.AnySymbol;
6import tools.refinery.store.query.RelationLike; 6import tools.refinery.store.query.Constraint;
7 7
8import java.util.Set; 8import java.util.Set;
9 9
10public sealed interface AnyRelationView extends RelationLike permits RelationView { 10public sealed interface AnyRelationView extends Constraint permits RelationView {
11 AnySymbol getSymbol(); 11 AnySymbol getSymbol();
12 12
13 String getViewName();
14
13 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() { 15 default Set<FunctionalDependency<Integer>> getFunctionalDependencies() {
14 return Set.of(); 16 return Set.of();
15 } 17 }
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
index 64c601bb..64c601bb 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/FilteredRelationView.java
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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class ForbiddenRelationView extends TuplePreservingRelationView<TruthValue> {
8 public ForbiddenRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "forbidden");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return !value.may();
15 }
16}
diff --git a/subprojects/store/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/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 @@
1package tools.refinery.store.query.view; 1package tools.refinery.store.query.view;
2 2
3import tools.refinery.store.model.Model; 3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.FunctionalDependency; 4import tools.refinery.store.query.dnf.FunctionalDependency;
5import tools.refinery.store.query.term.DataSort;
6import tools.refinery.store.query.term.NodeSort;
7import tools.refinery.store.query.term.Sort;
5import tools.refinery.store.representation.Symbol; 8import tools.refinery.store.representation.Symbol;
6import tools.refinery.store.tuple.Tuple; 9import tools.refinery.store.tuple.Tuple;
7import tools.refinery.store.tuple.Tuple1; 10import tools.refinery.store.tuple.Tuple1;
8 11
12import java.util.List;
13import java.util.Objects;
9import java.util.Set; 14import java.util.Set;
10import java.util.stream.Collectors; 15import java.util.stream.Collectors;
11import java.util.stream.IntStream; 16import java.util.stream.IntStream;
12 17
13public final class FunctionalRelationView<T> extends RelationView<T> { 18public final class FunctionalRelationView<T> extends RelationView<T> {
19 private final T defaultValue;
20
14 public FunctionalRelationView(Symbol<T> symbol, String name) { 21 public FunctionalRelationView(Symbol<T> symbol, String name) {
15 super(symbol, name); 22 super(symbol, name);
23 defaultValue = symbol.defaultValue();
16 } 24 }
17 25
18 public FunctionalRelationView(Symbol<T> symbol) { 26 public FunctionalRelationView(Symbol<T> symbol) {
19 super(symbol); 27 super(symbol);
28 defaultValue = symbol.defaultValue();
20 } 29 }
21 30
22 @Override 31 @Override
@@ -37,7 +46,7 @@ public final class FunctionalRelationView<T> extends RelationView<T> {
37 46
38 @Override 47 @Override
39 public boolean filter(Tuple key, T value) { 48 public boolean filter(Tuple key, T value) {
40 return true; 49 return !Objects.equals(defaultValue, value);
41 } 50 }
42 51
43 @Override 52 @Override
@@ -68,4 +77,29 @@ public final class FunctionalRelationView<T> extends RelationView<T> {
68 public int arity() { 77 public int arity() {
69 return getSymbol().arity() + 1; 78 return getSymbol().arity() + 1;
70 } 79 }
80
81 @Override
82 public List<Sort> getSorts() {
83 var sorts = new Sort[arity()];
84 int valueIndex = sorts.length - 1;
85 for (int i = 0; i < valueIndex; i++) {
86 sorts[i] = NodeSort.INSTANCE;
87 }
88 sorts[valueIndex] = new DataSort<>(getSymbol().valueType());
89 return List.of(sorts);
90 }
91
92 @Override
93 public boolean equals(Object o) {
94 if (this == o) return true;
95 if (o == null || getClass() != o.getClass()) return false;
96 if (!super.equals(o)) return false;
97 FunctionalRelationView<?> that = (FunctionalRelationView<?>) o;
98 return Objects.equals(defaultValue, that.defaultValue);
99 }
100
101 @Override
102 public int hashCode() {
103 return Objects.hash(super.hashCode(), defaultValue);
104 }
71} 105}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
index e1b2e45b..e1b2e45b 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/KeyOnlyRelationView.java
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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class MayRelationView extends TuplePreservingRelationView<TruthValue> {
8 public MayRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "may");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return value.may();
15 }
16}
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 @@
1package tools.refinery.store.query.view;
2
3import tools.refinery.store.representation.Symbol;
4import tools.refinery.store.representation.TruthValue;
5import tools.refinery.store.tuple.Tuple;
6
7public class MustRelationView extends TuplePreservingRelationView<TruthValue> {
8 public MustRelationView(Symbol<TruthValue> symbol) {
9 super(symbol, "must");
10 }
11
12 @Override
13 public boolean filter(Tuple key, TruthValue value) {
14 return value.must();
15 }
16}
diff --git a/subprojects/store/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 bbec1e73..d7164b3b 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationView.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationView.java
@@ -17,11 +17,11 @@ import java.util.UUID;
17public abstract non-sealed class RelationView<T> implements AnyRelationView { 17public abstract non-sealed class RelationView<T> implements AnyRelationView {
18 private final Symbol<T> symbol; 18 private final Symbol<T> symbol;
19 19
20 private final String name; 20 private final String viewName;
21 21
22 protected RelationView(Symbol<T> symbol, String name) { 22 protected RelationView(Symbol<T> symbol, String viewName) {
23 this.symbol = symbol; 23 this.symbol = symbol;
24 this.name = name; 24 this.viewName = viewName;
25 } 25 }
26 26
27 protected RelationView(Symbol<T> representation) { 27 protected RelationView(Symbol<T> representation) {
@@ -34,8 +34,13 @@ public abstract non-sealed class RelationView<T> implements AnyRelationView {
34 } 34 }
35 35
36 @Override 36 @Override
37 public String getViewName() {
38 return viewName;
39 }
40
41 @Override
37 public String name() { 42 public String name() {
38 return symbol.name() + "#" + name; 43 return symbol.name() + "#" + viewName;
39 } 44 }
40 45
41 public abstract boolean filter(Tuple key, T value); 46 public abstract boolean filter(Tuple key, T value);
@@ -48,15 +53,25 @@ public abstract non-sealed class RelationView<T> implements AnyRelationView {
48 } 53 }
49 54
50 @Override 55 @Override
56 public String toString() {
57 return name();
58 }
59
60 @Override
61 public String toReferenceString() {
62 return "@RelationView(\"%s\") %s".formatted(viewName, symbol.name());
63 }
64
65 @Override
51 public boolean equals(Object o) { 66 public boolean equals(Object o) {
52 if (this == o) return true; 67 if (this == o) return true;
53 if (o == null || getClass() != o.getClass()) return false; 68 if (o == null || getClass() != o.getClass()) return false;
54 RelationView<?> that = (RelationView<?>) o; 69 RelationView<?> that = (RelationView<?>) o;
55 return Objects.equals(symbol, that.symbol) && Objects.equals(name, that.name); 70 return Objects.equals(symbol, that.symbol) && Objects.equals(viewName, that.viewName);
56 } 71 }
57 72
58 @Override 73 @Override
59 public int hashCode() { 74 public int hashCode() {
60 return Objects.hash(symbol, name); 75 return Objects.hash(symbol, viewName);
61 } 76 }
62} 77}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
index 2ba1fcc4..2ba1fcc4 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
+++ b/subprojects/store-query/src/main/java/tools/refinery/store/query/view/RelationViewImplication.java
diff --git a/subprojects/store/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/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 @@
1package tools.refinery.store.query.view; 1package tools.refinery.store.query.view;
2 2
3import tools.refinery.store.model.Model; 3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.term.NodeSort;
5import tools.refinery.store.query.term.Sort;
4import tools.refinery.store.tuple.Tuple; 6import tools.refinery.store.tuple.Tuple;
5import tools.refinery.store.tuple.Tuple1; 7import tools.refinery.store.tuple.Tuple1;
6import tools.refinery.store.representation.Symbol; 8import tools.refinery.store.representation.Symbol;
7 9
10import java.util.Arrays;
11import java.util.List;
12
8public abstract class TuplePreservingRelationView<T> extends RelationView<T> { 13public abstract class TuplePreservingRelationView<T> extends RelationView<T> {
9 protected TuplePreservingRelationView(Symbol<T> symbol, String name) { 14 protected TuplePreservingRelationView(Symbol<T> symbol, String name) {
10 super(symbol, name); 15 super(symbol, name);
@@ -38,7 +43,15 @@ public abstract class TuplePreservingRelationView<T> extends RelationView<T> {
38 return filter(key, value); 43 return filter(key, value);
39 } 44 }
40 45
46 @Override
41 public int arity() { 47 public int arity() {
42 return this.getSymbol().arity(); 48 return this.getSymbol().arity();
43 } 49 }
50
51 @Override
52 public List<Sort> getSorts() {
53 var sorts = new Sort[arity()];
54 Arrays.fill(sorts, NodeSort.INSTANCE);
55 return List.of(sorts);
56 }
44} 57}
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
new file mode 100644
index 00000000..ceb46d6f
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/DnfBuilderTest.java
@@ -0,0 +1,220 @@
1package tools.refinery.store.query;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.literal.BooleanLiteral;
6import tools.refinery.store.query.term.Variable;
7import tools.refinery.store.query.view.KeyOnlyRelationView;
8import tools.refinery.store.representation.Symbol;
9
10import static org.hamcrest.MatcherAssert.assertThat;
11import static tools.refinery.store.query.literal.Literals.not;
12import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
13
14class DnfBuilderTest {
15 @Test
16 void eliminateTrueTest() {
17 var p = Variable.of("p");
18 var q = Variable.of("q");
19 var friend = new Symbol<>("friend", 2, Boolean.class, false);
20 var friendView = new KeyOnlyRelationView<>(friend);
21
22 var actual = Dnf.builder()
23 .parameters(p, q)
24 .clause(BooleanLiteral.TRUE, friendView.call(p, q))
25 .build();
26 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
27
28 assertThat(actual, structurallyEqualTo(expected));
29 }
30
31 @Test
32 void eliminateFalseTest() {
33 var p = Variable.of("p");
34 var q = Variable.of("q");
35 var friend = new Symbol<>("friend", 2, Boolean.class, false);
36 var friendView = new KeyOnlyRelationView<>(friend);
37
38 var actual = Dnf.builder()
39 .parameters(p, q)
40 .clause(friendView.call(p, q))
41 .clause(friendView.call(q, p), BooleanLiteral.FALSE)
42 .build();
43 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
44
45 assertThat(actual, structurallyEqualTo(expected));
46 }
47
48 @Test
49 void alwaysTrueTest() {
50 var p = Variable.of("p");
51 var q = Variable.of("q");
52 var friend = new Symbol<>("friend", 2, Boolean.class, false);
53 var friendView = new KeyOnlyRelationView<>(friend);
54
55 var actual = Dnf.builder()
56 .parameters(p, q)
57 .clause(friendView.call(p, q))
58 .clause(BooleanLiteral.TRUE)
59 .build();
60 var expected = Dnf.builder().parameters(p, q).clause().build();
61
62 assertThat(actual, structurallyEqualTo(expected));
63 }
64
65 @Test
66 void alwaysFalseTest() {
67 var p = Variable.of("p");
68 var q = Variable.of("q");
69 var friend = new Symbol<>("friend", 2, Boolean.class, false);
70 var friendView = new KeyOnlyRelationView<>(friend);
71
72 var actual = Dnf.builder()
73 .parameters(p, q)
74 .clause(friendView.call(p, q), BooleanLiteral.FALSE)
75 .build();
76 var expected = Dnf.builder().parameters(p, q).build();
77
78 assertThat(actual, structurallyEqualTo(expected));
79 }
80
81 @Test
82 void eliminateTrueDnfTest() {
83 var p = Variable.of("p");
84 var q = Variable.of("q");
85 var friend = new Symbol<>("friend", 2, Boolean.class, false);
86 var friendView = new KeyOnlyRelationView<>(friend);
87 var trueDnf = Dnf.builder().parameter(p).clause().build();
88
89 var actual = Dnf.builder()
90 .parameters(p, q)
91 .clause(trueDnf.call(q), friendView.call(p, q))
92 .build();
93 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
94
95 assertThat(actual, structurallyEqualTo(expected));
96 }
97
98 @Test
99 void eliminateFalseDnfTest() {
100 var p = Variable.of("p");
101 var q = Variable.of("q");
102 var friend = new Symbol<>("friend", 2, Boolean.class, false);
103 var friendView = new KeyOnlyRelationView<>(friend);
104 var falseDnf = Dnf.builder().parameter(p).build();
105
106 var actual = Dnf.builder()
107 .parameters(p, q)
108 .clause(friendView.call(p, q))
109 .clause(friendView.call(q, p), falseDnf.call(q))
110 .build();
111 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
112
113 assertThat(actual, structurallyEqualTo(expected));
114 }
115
116 @Test
117 void alwaysTrueDnfTest() {
118 var p = Variable.of("p");
119 var q = Variable.of("q");
120 var friend = new Symbol<>("friend", 2, Boolean.class, false);
121 var friendView = new KeyOnlyRelationView<>(friend);
122 var trueDnf = Dnf.builder().parameter(p).clause().build();
123
124 var actual = Dnf.builder()
125 .parameters(p, q)
126 .clause(friendView.call(p, q))
127 .clause(trueDnf.call(q))
128 .build();
129 var expected = Dnf.builder().parameters(p, q).clause().build();
130
131 assertThat(actual, structurallyEqualTo(expected));
132 }
133
134 @Test
135 void alwaysFalseDnfTest() {
136 var p = Variable.of("p");
137 var q = Variable.of("q");
138 var friend = new Symbol<>("friend", 2, Boolean.class, false);
139 var friendView = new KeyOnlyRelationView<>(friend);
140 var falseDnf = Dnf.builder().parameter(p).build();
141
142 var actual = Dnf.builder()
143 .parameters(p, q)
144 .clause(friendView.call(p, q), falseDnf.call(q))
145 .build();
146 var expected = Dnf.builder().parameters(p, q).build();
147
148 assertThat(actual, structurallyEqualTo(expected));
149 }
150
151 @Test
152 void eliminateNotFalseDnfTest() {
153 var p = Variable.of("p");
154 var q = Variable.of("q");
155 var friend = new Symbol<>("friend", 2, Boolean.class, false);
156 var friendView = new KeyOnlyRelationView<>(friend);
157 var falseDnf = Dnf.builder().parameter(p).build();
158
159 var actual = Dnf.builder()
160 .parameters(p, q)
161 .clause(not(falseDnf.call(q)), friendView.call(p, q))
162 .build();
163 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
164
165 assertThat(actual, structurallyEqualTo(expected));
166 }
167
168 @Test
169 void eliminateNotTrueDnfTest() {
170 var p = Variable.of("p");
171 var q = Variable.of("q");
172 var friend = new Symbol<>("friend", 2, Boolean.class, false);
173 var friendView = new KeyOnlyRelationView<>(friend);
174 var trueDnf = Dnf.builder().parameter(p).clause().build();
175
176 var actual = Dnf.builder()
177 .parameters(p, q)
178 .clause(friendView.call(p, q))
179 .clause(friendView.call(q, p), not(trueDnf.call(q)))
180 .build();
181 var expected = Dnf.builder().parameters(p, q).clause(friendView.call(p, q)).build();
182
183 assertThat(actual, structurallyEqualTo(expected));
184 }
185
186 @Test
187 void alwaysNotFalseDnfTest() {
188 var p = Variable.of("p");
189 var q = Variable.of("q");
190 var friend = new Symbol<>("friend", 2, Boolean.class, false);
191 var friendView = new KeyOnlyRelationView<>(friend);
192 var falseDnf = Dnf.builder().parameter(p).build();
193
194 var actual = Dnf.builder()
195 .parameters(p, q)
196 .clause(friendView.call(p, q))
197 .clause(not(falseDnf.call(q)))
198 .build();
199 var expected = Dnf.builder().parameters(p, q).clause().build();
200
201 assertThat(actual, structurallyEqualTo(expected));
202 }
203
204 @Test
205 void alwaysNotTrueDnfTest() {
206 var p = Variable.of("p");
207 var q = Variable.of("q");
208 var friend = new Symbol<>("friend", 2, Boolean.class, false);
209 var friendView = new KeyOnlyRelationView<>(friend);
210 var trueDnf = Dnf.builder().parameter(p).clause().build();
211
212 var actual = Dnf.builder()
213 .parameters(p, q)
214 .clause(friendView.call(p, q), not(trueDnf.call(q)))
215 .build();
216 var expected = Dnf.builder().parameters(p, q).build();
217
218 assertThat(actual, structurallyEqualTo(expected));
219 }
220}
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 @@
1package tools.refinery.store.query;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.term.Variable;
6import tools.refinery.store.query.view.KeyOnlyRelationView;
7import tools.refinery.store.representation.Symbol;
8
9import static org.hamcrest.MatcherAssert.assertThat;
10import static org.hamcrest.Matchers.is;
11import static tools.refinery.store.query.literal.Literals.not;
12
13class DnfToDefinitionStringTest {
14 @Test
15 void noClausesTest() {
16 var p = Variable.of("p");
17 var dnf = Dnf.builder("Example").parameter(p).build();
18
19 assertThat(dnf.toDefinitionString(), is("""
20 pred Example(p) <->
21 <no clauses>.
22 """));
23 }
24
25 @Test
26 void noParametersTest() {
27 var dnf = Dnf.builder("Example").build();
28
29 assertThat(dnf.toDefinitionString(), is("""
30 pred Example() <->
31 <no clauses>.
32 """));
33 }
34
35 @Test
36 void emptyClauseTest() {
37 var p = Variable.of("p");
38 var dnf = Dnf.builder("Example").parameter(p).clause().build();
39
40 assertThat(dnf.toDefinitionString(), is("""
41 pred Example(p) <->
42 <empty>.
43 """));
44 }
45
46 @Test
47 void relationViewPositiveTest() {
48 var p = Variable.of("p");
49 var q = Variable.of("q");
50 var friend = new Symbol<>("friend", 2, Boolean.class, false);
51 var friendView = new KeyOnlyRelationView<>(friend);
52 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.call(p, q)).build();
53
54 assertThat(dnf.toDefinitionString(), is("""
55 pred Example(p) <->
56 @RelationView("key") friend(p, q).
57 """));
58 }
59
60 @Test
61 void relationViewNegativeTest() {
62 var p = Variable.of("p");
63 var q = Variable.of("q");
64 var friend = new Symbol<>("friend", 2, Boolean.class, false);
65 var friendView = new KeyOnlyRelationView<>(friend);
66 var dnf = Dnf.builder("Example").parameter(p).clause(not(friendView.call(p, q))).build();
67
68 assertThat(dnf.toDefinitionString(), is("""
69 pred Example(p) <->
70 !(@RelationView("key") friend(p, q)).
71 """));
72 }
73
74 @Test
75 void relationViewTransitiveTest() {
76 var p = Variable.of("p");
77 var q = Variable.of("q");
78 var friend = new Symbol<>("friend", 2, Boolean.class, false);
79 var friendView = new KeyOnlyRelationView<>(friend);
80 var dnf = Dnf.builder("Example").parameter(p).clause(friendView.callTransitive(p, q)).build();
81
82 assertThat(dnf.toDefinitionString(), is("""
83 pred Example(p) <->
84 @RelationView("key") friend+(p, q).
85 """));
86 }
87
88 @Test
89 void multipleParametersTest() {
90 var p = Variable.of("p");
91 var q = Variable.of("q");
92 var friend = new Symbol<>("friend", 2, Boolean.class, false);
93 var friendView = new KeyOnlyRelationView<>(friend);
94 var dnf = Dnf.builder("Example").parameters(p, q).clause(friendView.call(p, q)).build();
95
96 assertThat(dnf.toDefinitionString(), is("""
97 pred Example(p, q) <->
98 @RelationView("key") friend(p, q).
99 """));
100 }
101
102 @Test
103 void multipleLiteralsTest() {
104 var p = Variable.of("p");
105 var q = Variable.of("q");
106 var person = new Symbol<>("person", 1, Boolean.class, false);
107 var personView = new KeyOnlyRelationView<>(person);
108 var friend = new Symbol<>("friend", 2, Boolean.class, false);
109 var friendView = new KeyOnlyRelationView<>(friend);
110 var dnf = Dnf.builder("Example")
111 .parameter(p)
112 .clause(
113 personView.call(p),
114 personView.call(q),
115 friendView.call(p, q)
116 )
117 .build();
118
119 assertThat(dnf.toDefinitionString(), is("""
120 pred Example(p) <->
121 @RelationView("key") person(p),
122 @RelationView("key") person(q),
123 @RelationView("key") friend(p, q).
124 """));
125 }
126
127 @Test
128 void multipleClausesTest() {
129 var p = Variable.of("p");
130 var q = Variable.of("q");
131 var friend = new Symbol<>("friend", 2, Boolean.class, false);
132 var friendView = new KeyOnlyRelationView<>(friend);
133 var dnf = Dnf.builder("Example")
134 .parameter(p)
135 .clause(friendView.call(p, q))
136 .clause(friendView.call(q, p))
137 .build();
138
139 assertThat(dnf.toDefinitionString(), is("""
140 pred Example(p) <->
141 @RelationView("key") friend(p, q)
142 ;
143 @RelationView("key") friend(q, p).
144 """));
145 }
146
147 @Test
148 void dnfTest() {
149 var p = Variable.of("p");
150 var q = Variable.of("q");
151 var r = Variable.of("r");
152 var s = Variable.of("s");
153 var person = new Symbol<>("person", 1, Boolean.class, false);
154 var personView = new KeyOnlyRelationView<>(person);
155 var friend = new Symbol<>("friend", 2, Boolean.class, false);
156 var friendView = new KeyOnlyRelationView<>(friend);
157 var called = Dnf.builder("Called").parameters(r, s).clause(friendView.call(r, s)).build();
158 var dnf = Dnf.builder("Example")
159 .parameter(p)
160 .clause(
161 personView.call(p),
162 personView.call(q),
163 not(called.call(p, q))
164 )
165 .build();
166
167 assertThat(dnf.toDefinitionString(), is("""
168 pred Example(p) <->
169 @RelationView("key") person(p),
170 @RelationView("key") person(q),
171 !(@Dnf Called(p, q)).
172 """));
173 }
174}
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
new file mode 100644
index 00000000..a61e2b65
--- /dev/null
+++ b/subprojects/store-query/src/test/java/tools/refinery/store/query/tests/StructurallyEqualToTest.java
@@ -0,0 +1,77 @@
1package tools.refinery.store.query.tests;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.term.Variable;
6import tools.refinery.store.query.view.KeyOnlyRelationView;
7import tools.refinery.store.representation.Symbol;
8
9import static org.hamcrest.CoreMatchers.containsString;
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.junit.jupiter.api.Assertions.assertThrows;
12import static tools.refinery.store.query.tests.QueryMatchers.structurallyEqualTo;
13
14class StructurallyEqualToTest {
15 @Test
16 void flatEqualsTest() {
17 var p = Variable.of("p");
18 var q = Variable.of("q");
19 var person = new Symbol<>("Person", 1, Boolean.class, false);
20 var personView = new KeyOnlyRelationView<>(person);
21
22 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
23 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(p)).build();
24
25 assertThat(actual, structurallyEqualTo(expected));
26 }
27
28 @Test
29 void flatNotEqualsTest() {
30 var p = Variable.of("p");
31 var q = Variable.of("q");
32 var person = new Symbol<>("Person", 1, Boolean.class, false);
33 var personView = new KeyOnlyRelationView<>(person);
34
35 var expected = Dnf.builder("Expected").parameters(q).clause(personView.call(q)).build();
36 var actual = Dnf.builder("Actual").parameters(p).clause(personView.call(q)).build();
37
38 var assertion = structurallyEqualTo(expected);
39 assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
40 }
41
42 @Test
43 void deepEqualsTest() {
44 var p = Variable.of("p");
45 var q = Variable.of("q");
46 var person = new Symbol<>("Person", 1, Boolean.class, false);
47 var personView = new KeyOnlyRelationView<>(person);
48
49 var expected = Dnf.builder("Expected").parameters(q).clause(
50 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
51 ).build();
52 var actual = Dnf.builder("Actual").parameters(q).clause(
53 Dnf.builder("Actual2").parameters(p).clause(personView.call(p)).build().call(q)
54 ).build();
55
56 assertThat(actual, structurallyEqualTo(expected));
57 }
58
59 @Test
60 void deepNotEqualsTest() {
61 var p = Variable.of("p");
62 var q = Variable.of("q");
63 var person = new Symbol<>("Person", 1, Boolean.class, false);
64 var personView = new KeyOnlyRelationView<>(person);
65
66 var expected = Dnf.builder("Expected").parameters(q).clause(
67 Dnf.builder("Expected2").parameters(p).clause(personView.call(p)).build().call(q)
68 ).build();
69 var actual = Dnf.builder("Actual").parameters(q).clause(
70 Dnf.builder("Actual2").parameters(p).clause(personView.call(q)).build().call(q)
71 ).build();
72
73 var assertion = structurallyEqualTo(expected);
74 var error = assertThrows(AssertionError.class, () -> assertThat(actual, assertion));
75 assertThat(error.getMessage(), containsString(" called from Expected/1 "));
76 }
77}
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
new file mode 100644
index 00000000..685957c9
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/MismatchDescribingDnfEqualityChecker.java
@@ -0,0 +1,43 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Description;
4import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
5
6class MismatchDescribingDnfEqualityChecker extends DeepDnfEqualityChecker {
7 private final Description description;
8 private boolean described;
9
10 MismatchDescribingDnfEqualityChecker(Description description) {
11 this.description = description;
12 }
13
14 public boolean isDescribed() {
15 return described;
16 }
17
18 @Override
19 protected boolean doCheckEqual(Pair pair) {
20 boolean result = super.doCheckEqual(pair);
21 if (!result && !described) {
22 describeMismatch(pair);
23 // Only describe the first found (innermost) mismatch.
24 described = true;
25 }
26 return result;
27 }
28
29 private void describeMismatch(Pair pair) {
30 var inProgress = getInProgress();
31 int size = inProgress.size();
32 if (size <= 1) {
33 description.appendText("was ").appendText(pair.left().toDefinitionString());
34 return;
35 }
36 var last = inProgress.get(size - 1);
37 description.appendText("expected ").appendText(last.right().toDefinitionString());
38 for (int i = size - 2; i >= 0; i--) {
39 description.appendText(" called from ").appendText(inProgress.get(i).left().toString());
40 }
41 description.appendText(" was not structurally equal to ").appendText(last.right().toDefinitionString());
42 }
43}
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
new file mode 100644
index 00000000..bf1c1b74
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/QueryMatchers.java
@@ -0,0 +1,14 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Matcher;
4import tools.refinery.store.query.dnf.Dnf;
5
6public final class QueryMatchers {
7 private QueryMatchers() {
8 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
9 }
10
11 public static Matcher<Dnf> structurallyEqualTo(Dnf expected) {
12 return new StructurallyEqualTo(expected);
13 }
14}
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
new file mode 100644
index 00000000..a9a78f88
--- /dev/null
+++ b/subprojects/store-query/src/testFixtures/java/tools/refinery/store/query/tests/StructurallyEqualTo.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.query.tests;
2
3import org.hamcrest.Description;
4import org.hamcrest.TypeSafeMatcher;
5import tools.refinery.store.query.dnf.Dnf;
6import tools.refinery.store.query.equality.DeepDnfEqualityChecker;
7
8public class StructurallyEqualTo extends TypeSafeMatcher<Dnf> {
9 private final Dnf expected;
10
11 public StructurallyEqualTo(Dnf expected) {
12 this.expected = expected;
13 }
14
15 @Override
16 protected boolean matchesSafely(Dnf item) {
17 var checker = new DeepDnfEqualityChecker();
18 return checker.dnfEqual(expected, item);
19 }
20
21 @Override
22 protected void describeMismatchSafely(Dnf item, Description mismatchDescription) {
23 var describingChecker = new MismatchDescribingDnfEqualityChecker(mismatchDescription);
24 if (describingChecker.dnfEqual(expected, item)) {
25 throw new IllegalStateException("Mismatched Dnf was matching on repeated comparison");
26 }
27 if (!describingChecker.isDescribed()) {
28 super.describeMismatchSafely(item, mismatchDescription);
29 }
30 }
31
32 @Override
33 public void describeTo(Description description) {
34 description.appendText("structurally equal to ").appendText(expected.toDefinitionString());
35 }
36}
diff --git a/subprojects/store-reasoning/build.gradle b/subprojects/store-reasoning/build.gradle
new file mode 100644
index 00000000..cb440d9f
--- /dev/null
+++ b/subprojects/store-reasoning/build.gradle
@@ -0,0 +1,7 @@
1plugins {
2 id 'refinery-java-library'
3}
4
5dependencies {
6 api project(':refinery-store-query')
7}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java
new file mode 100644
index 00000000..ebe82c8b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/AnyPartialInterpretation.java
@@ -0,0 +1,13 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
4
5public sealed interface AnyPartialInterpretation permits PartialInterpretation {
6 ReasoningAdapter getAdapter();
7
8 AnyPartialSymbol getPartialSymbol();
9
10 int countUnfinished();
11
12 int countErrors();
13}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java
new file mode 100644
index 00000000..0d51598b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/MergeResult.java
@@ -0,0 +1,15 @@
1package tools.refinery.store.reasoning;
2
3public enum MergeResult {
4 UNCHANGED,
5 REFINED,
6 REJECTED;
7
8 public MergeResult andAlso(MergeResult other) {
9 return switch (this) {
10 case UNCHANGED -> other;
11 case REFINED -> other == REJECTED ? REJECTED : REFINED;
12 case REJECTED -> REJECTED;
13 };
14 }
15}
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
new file mode 100644
index 00000000..4f195e97
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/PartialInterpretation.java
@@ -0,0 +1,20 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.reasoning.representation.PartialSymbol;
4import tools.refinery.store.map.Cursor;
5import tools.refinery.store.tuple.Tuple;
6
7public non-sealed interface PartialInterpretation<A, C> extends AnyPartialInterpretation {
8 @Override
9 PartialSymbol<A, C> getPartialSymbol();
10
11 A get(Tuple key);
12
13 Cursor<Tuple, A> getAll();
14
15 MergeResult merge(Tuple key, A value);
16
17 C getConcrete(Tuple key);
18
19 Cursor<Tuple, C> getAllConcrete();
20}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/Reasoning.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/Reasoning.java
new file mode 100644
index 00000000..d7d0a999
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/Reasoning.java
@@ -0,0 +1,24 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.reasoning.internal.ReasoningBuilderImpl;
4import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
5import tools.refinery.store.model.ModelStoreBuilder;
6import tools.refinery.store.reasoning.representation.PartialRelation;
7
8public final class Reasoning extends ModelAdapterBuilderFactory<ReasoningAdapter,
9 ReasoningStoreAdapter, ReasoningBuilder> {
10 public static final Reasoning ADAPTER = new Reasoning();
11
12 public static final PartialRelation EXISTS = new PartialRelation("exists", 1);
13
14 public static final PartialRelation EQUALS = new PartialRelation("equals", 1);
15
16 private Reasoning() {
17 super(ReasoningAdapter.class, ReasoningStoreAdapter.class, ReasoningBuilder.class);
18 }
19
20 @Override
21 public ReasoningBuilder createBuilder(ModelStoreBuilder storeBuilder) {
22 return new ReasoningBuilderImpl(storeBuilder);
23 }
24}
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
new file mode 100644
index 00000000..de039dd9
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningAdapter.java
@@ -0,0 +1,22 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.adapter.ModelAdapter;
4import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
5import tools.refinery.store.reasoning.representation.PartialSymbol;
6import tools.refinery.store.query.dnf.Dnf;
7import tools.refinery.store.query.ResultSet;
8
9public interface ReasoningAdapter extends ModelAdapter {
10 @Override
11 ReasoningStoreAdapter getStoreAdapter();
12
13 default AnyPartialInterpretation getPartialInterpretation(AnyPartialSymbol partialSymbol) {
14 // Cast to disambiguate overloads.
15 var typedPartialSymbol = (PartialSymbol<?, ?>) partialSymbol;
16 return getPartialInterpretation(typedPartialSymbol);
17 }
18
19 <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol);
20
21 ResultSet getLiftedResultSet(Dnf query);
22}
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
new file mode 100644
index 00000000..4030d296
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningBuilder.java
@@ -0,0 +1,28 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.reasoning.literal.Modality;
6import tools.refinery.store.query.dnf.Dnf;
7
8import java.util.Collection;
9import java.util.List;
10
11@SuppressWarnings("UnusedReturnValue")
12public interface ReasoningBuilder extends ModelAdapterBuilder {
13 default ReasoningBuilder liftedQueries(Dnf... liftedQueries) {
14 return liftedQueries(List.of(liftedQueries));
15 }
16
17 default ReasoningBuilder liftedQueries(Collection<Dnf> liftedQueries) {
18 liftedQueries.forEach(this::liftedQuery);
19 return this;
20 }
21
22 ReasoningBuilder liftedQuery(Dnf liftedQuery);
23
24 Dnf lift(Modality modality, Dnf query);
25
26 @Override
27 ReasoningStoreAdapter createStoreAdapter(ModelStore store);
28}
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
new file mode 100644
index 00000000..f6a6e414
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/ReasoningStoreAdapter.java
@@ -0,0 +1,17 @@
1package tools.refinery.store.reasoning;
2
3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
6import tools.refinery.store.query.dnf.Dnf;
7
8import java.util.Collection;
9
10public interface ReasoningStoreAdapter extends ModelStoreAdapter {
11 Collection<AnyPartialSymbol> getPartialSymbols();
12
13 Collection<Dnf> getLiftedQueries();
14
15 @Override
16 ReasoningAdapter createModelAdapter(Model model);
17}
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
new file mode 100644
index 00000000..0acf0d49
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningAdapterImpl.java
@@ -0,0 +1,38 @@
1package tools.refinery.store.reasoning.internal;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.reasoning.ReasoningAdapter;
5import tools.refinery.store.reasoning.PartialInterpretation;
6import tools.refinery.store.reasoning.representation.PartialSymbol;
7import tools.refinery.store.query.dnf.Dnf;
8import tools.refinery.store.query.ResultSet;
9
10public class ReasoningAdapterImpl implements ReasoningAdapter {
11 private final Model model;
12 private final ReasoningStoreAdapterImpl storeAdapter;
13
14 ReasoningAdapterImpl(Model model, ReasoningStoreAdapterImpl storeAdapter) {
15 this.model = model;
16 this.storeAdapter = storeAdapter;
17 }
18
19 @Override
20 public Model getModel() {
21 return model;
22 }
23
24 @Override
25 public ReasoningStoreAdapterImpl getStoreAdapter() {
26 return storeAdapter;
27 }
28
29 @Override
30 public <A, C> PartialInterpretation<A, C> getPartialInterpretation(PartialSymbol<A, C> partialSymbol) {
31 return null;
32 }
33
34 @Override
35 public ResultSet getLiftedResultSet(Dnf query) {
36 return null;
37 }
38}
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
new file mode 100644
index 00000000..e11b14bf
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningBuilderImpl.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.reasoning.internal;
2
3import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.model.ModelStoreBuilder;
6import tools.refinery.store.reasoning.ReasoningBuilder;
7import tools.refinery.store.reasoning.literal.Modality;
8import tools.refinery.store.query.dnf.Dnf;
9
10public class ReasoningBuilderImpl extends AbstractModelAdapterBuilder implements ReasoningBuilder {
11 public ReasoningBuilderImpl(ModelStoreBuilder storeBuilder) {
12 super(storeBuilder);
13 }
14
15 @Override
16 public ReasoningBuilder liftedQuery(Dnf liftedQuery) {
17 return null;
18 }
19
20 @Override
21 public Dnf lift(Modality modality, Dnf query) {
22 return null;
23 }
24
25 @Override
26 public ReasoningStoreAdapterImpl createStoreAdapter(ModelStore store) {
27 return null;
28 }
29}
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
new file mode 100644
index 00000000..ac06e68b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/internal/ReasoningStoreAdapterImpl.java
@@ -0,0 +1,37 @@
1package tools.refinery.store.reasoning.internal;
2
3import tools.refinery.store.reasoning.ReasoningStoreAdapter;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.model.ModelStore;
6import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
7import tools.refinery.store.query.dnf.Dnf;
8
9import java.util.Collection;
10
11public class ReasoningStoreAdapterImpl implements ReasoningStoreAdapter {
12 private final ModelStore store;
13
14 ReasoningStoreAdapterImpl(ModelStore store) {
15 this.store = store;
16 }
17
18 @Override
19 public ModelStore getStore() {
20 return store;
21 }
22
23 @Override
24 public Collection<AnyPartialSymbol> getPartialSymbols() {
25 return null;
26 }
27
28 @Override
29 public Collection<Dnf> getLiftedQueries() {
30 return null;
31 }
32
33 @Override
34 public ReasoningAdapterImpl createModelAdapter(Model model) {
35 return new ReasoningAdapterImpl(model, this);
36 }
37}
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
new file mode 100644
index 00000000..2b0e0f08
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/DnfLifter.java
@@ -0,0 +1,116 @@
1package tools.refinery.store.reasoning.lifting;
2
3import org.jetbrains.annotations.Nullable;
4import tools.refinery.store.query.dnf.Dnf;
5import tools.refinery.store.query.dnf.DnfBuilder;
6import tools.refinery.store.query.dnf.DnfClause;
7import tools.refinery.store.query.literal.CallLiteral;
8import tools.refinery.store.query.literal.CallPolarity;
9import tools.refinery.store.query.literal.Literal;
10import tools.refinery.store.query.term.DataVariable;
11import tools.refinery.store.query.term.Variable;
12import tools.refinery.store.reasoning.Reasoning;
13import tools.refinery.store.reasoning.literal.ModalConstraint;
14import tools.refinery.store.reasoning.literal.Modality;
15import tools.refinery.store.reasoning.literal.PartialLiterals;
16import tools.refinery.store.util.CycleDetectingMapper;
17
18import java.util.ArrayList;
19import java.util.HashSet;
20import java.util.List;
21
22public class DnfLifter {
23 private final CycleDetectingMapper<ModalDnf, Dnf> mapper = new CycleDetectingMapper<>(ModalDnf::toString,
24 this::doLift);
25
26 public Dnf lift(Modality modality, Dnf query) {
27 return mapper.map(new ModalDnf(modality, query));
28 }
29
30 private Dnf doLift(ModalDnf modalDnf) {
31 var modality = modalDnf.modality();
32 var dnf = modalDnf.dnf();
33 var builder = Dnf.builder();
34 builder.parameters(dnf.getParameters());
35 boolean changed = false;
36 for (var clause : dnf.getClauses()) {
37 if (liftClause(modality, clause, builder)) {
38 changed = true;
39 }
40 }
41 if (changed) {
42 return builder.build();
43 }
44 return dnf;
45 }
46
47 private boolean liftClause(Modality modality, DnfClause clause, DnfBuilder builder) {
48 boolean changed = false;
49 var quantifiedVariables = new HashSet<>(clause.boundVariables()
50 .stream()
51 .filter(DataVariable.class::isInstance)
52 .toList());
53 var literals = clause.literals();
54 var liftedLiterals = new ArrayList<Literal>(literals.size());
55 for (var literal : literals) {
56 Literal liftedLiteral = liftLiteral(modality, literal);
57 if (liftedLiteral == null) {
58 liftedLiteral = literal;
59 } else {
60 changed = true;
61 }
62 liftedLiterals.add(liftedLiteral);
63 var variable = isExistsLiteralForVariable(modality, liftedLiteral);
64 if (variable != null) {
65 // If we already quantify over the existence of the variable with the expected modality,
66 // we don't need to insert quantification manually.
67 quantifiedVariables.remove(variable);
68 }
69 }
70 for (var quantifiedVariable : quantifiedVariables) {
71 // Quantify over data variables that are not already quantified with the expected modality.
72 liftedLiterals.add(new CallLiteral(CallPolarity.POSITIVE, new ModalConstraint(modality, Reasoning.EXISTS),
73 List.of(quantifiedVariable)));
74 }
75 builder.clause(liftedLiterals);
76 return changed || !quantifiedVariables.isEmpty();
77 }
78
79 @Nullable
80 private Variable isExistsLiteralForVariable(Modality modality, Literal literal) {
81 if (literal instanceof CallLiteral callLiteral &&
82 callLiteral.getPolarity() == CallPolarity.POSITIVE &&
83 callLiteral.getTarget() instanceof ModalConstraint modalConstraint &&
84 modalConstraint.modality() == modality &&
85 modalConstraint.constraint().equals(Reasoning.EXISTS)) {
86 return callLiteral.getArguments().get(0);
87 }
88 return null;
89 }
90
91 @Nullable
92 private Literal liftLiteral(Modality modality, Literal literal) {
93 if (!(literal instanceof CallLiteral callLiteral)) {
94 return null;
95 }
96 var target = callLiteral.getTarget();
97 if (target instanceof ModalConstraint modalTarget) {
98 var actualTarget = modalTarget.constraint();
99 if (actualTarget instanceof Dnf dnf) {
100 var targetModality = modalTarget.modality();
101 var liftedTarget = lift(targetModality, dnf);
102 return new CallLiteral(callLiteral.getPolarity(), liftedTarget, callLiteral.getArguments());
103 }
104 // No more lifting to be done, pass any modal call to a partial symbol through.
105 return null;
106 } else if (target instanceof Dnf dnf) {
107 var polarity = callLiteral.getPolarity();
108 var liftedTarget = lift(modality.commute(polarity), dnf);
109 // Use == instead of equals(), because lift will return the same object by reference is there are no
110 // changes made during lifting.
111 return liftedTarget == target ? null : new CallLiteral(polarity, liftedTarget, callLiteral.getArguments());
112 } else {
113 return PartialLiterals.addModality(callLiteral, modality);
114 }
115 }
116}
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
new file mode 100644
index 00000000..ec381bb8
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/lifting/ModalDnf.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.reasoning.lifting;
2
3import tools.refinery.store.query.dnf.Dnf;
4import tools.refinery.store.reasoning.literal.Modality;
5
6record ModalDnf(Modality modality, Dnf dnf) {
7 @Override
8 public String toString() {
9 return "%s %s".formatted(modality, dnf.name());
10 }
11}
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 @@
1package tools.refinery.store.reasoning.literal;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.equality.LiteralEqualityHelper;
5import tools.refinery.store.query.literal.LiteralReduction;
6import tools.refinery.store.query.term.Sort;
7
8import java.util.List;
9
10public record ModalConstraint(Modality modality, Constraint constraint) implements Constraint {
11 private static final String FORMAT = "%s %s";
12
13 @Override
14 public String name() {
15 return FORMAT.formatted(modality, constraint.name());
16 }
17
18 @Override
19 public List<Sort> getSorts() {
20 return constraint.getSorts();
21 }
22
23 @Override
24 public LiteralReduction getReduction() {
25 return constraint.getReduction();
26 }
27
28 @Override
29 public boolean equals(LiteralEqualityHelper helper, Constraint other) {
30 if (getClass() != other.getClass()) {
31 return false;
32 }
33 var otherModalConstraint = (ModalConstraint) other;
34 return modality == otherModalConstraint.modality && constraint.equals(helper, otherModalConstraint.constraint);
35 }
36
37 @Override
38 public String toReferenceString() {
39 return FORMAT.formatted(modality, constraint.toReferenceString());
40 }
41
42 @Override
43 public String toString() {
44 return FORMAT.formatted(modality, constraint);
45 }
46}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java
index e389f563..f0cb59de 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/Modality.java
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/Modality.java
@@ -1,4 +1,6 @@
1package tools.refinery.store.query.atom; 1package tools.refinery.store.reasoning.literal;
2
3import tools.refinery.store.query.literal.CallPolarity;
2 4
3import java.util.Locale; 5import java.util.Locale;
4 6
@@ -15,6 +17,13 @@ public enum Modality {
15 }; 17 };
16 } 18 }
17 19
20 public Modality commute(CallPolarity polarity) {
21 if (polarity.isPositive()) {
22 return this;
23 }
24 return this.negate();
25 }
26
18 @Override 27 @Override
19 public String toString() { 28 public String toString() {
20 return name().toLowerCase(Locale.ROOT); 29 return name().toLowerCase(Locale.ROOT);
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
new file mode 100644
index 00000000..f991f87f
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/literal/PartialLiterals.java
@@ -0,0 +1,31 @@
1package tools.refinery.store.reasoning.literal;
2
3import tools.refinery.store.query.literal.CallLiteral;
4
5public final class PartialLiterals {
6 private PartialLiterals() {
7 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
8 }
9
10 public static CallLiteral may(CallLiteral literal) {
11 return addModality(literal, Modality.MAY);
12 }
13
14 public static CallLiteral must(CallLiteral literal) {
15 return addModality(literal, Modality.MUST);
16 }
17
18 public static CallLiteral current(CallLiteral literal) {
19 return addModality(literal, Modality.CURRENT);
20 }
21
22 public static CallLiteral addModality(CallLiteral literal, Modality modality) {
23 var target = literal.getTarget();
24 if (target instanceof ModalConstraint) {
25 throw new IllegalArgumentException("Literal %s already has modality".formatted(literal));
26 }
27 var polarity = literal.getPolarity();
28 var modalTarget = new ModalConstraint(modality.commute(polarity), target);
29 return new CallLiteral(polarity, modalTarget, literal.getArguments());
30 }
31}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java
new file mode 100644
index 00000000..e74cd58b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialFunction.java
@@ -0,0 +1,4 @@
1package tools.refinery.store.reasoning.representation;
2
3public sealed interface AnyPartialFunction extends AnyPartialSymbol permits PartialFunction {
4}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java
new file mode 100644
index 00000000..6ff5031b
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/AnyPartialSymbol.java
@@ -0,0 +1,11 @@
1package tools.refinery.store.reasoning.representation;
2
3import tools.refinery.store.representation.AnyAbstractDomain;
4
5public sealed interface AnyPartialSymbol permits AnyPartialFunction, PartialSymbol {
6 String name();
7
8 int arity();
9
10 AnyAbstractDomain abstractDomain();
11}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java
new file mode 100644
index 00000000..59eeeefe
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialFunction.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.reasoning.representation;
2
3import tools.refinery.store.representation.AbstractDomain;
4
5public record PartialFunction<A, C>(String name, int arity, AbstractDomain<A, C> abstractDomain)
6 implements AnyPartialFunction, PartialSymbol<A, C> {
7 @Override
8 public A defaultValue() {
9 return null;
10 }
11
12 @Override
13 public C defaultConcreteValue() {
14 return null;
15 }
16
17 @Override
18 public boolean equals(Object o) {
19 return this == o;
20 }
21
22 @Override
23 public int hashCode() {
24 // Compare by identity to make hash table lookups more efficient.
25 return System.identityHashCode(this);
26 }
27
28 @Override
29 public String toString() {
30 return "%s/%d".formatted(name, arity);
31 }
32}
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
new file mode 100644
index 00000000..9bae53a9
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialRelation.java
@@ -0,0 +1,56 @@
1package tools.refinery.store.reasoning.representation;
2
3import tools.refinery.store.query.Constraint;
4import tools.refinery.store.query.term.NodeSort;
5import tools.refinery.store.query.term.Sort;
6import tools.refinery.store.representation.AbstractDomain;
7import tools.refinery.store.representation.TruthValue;
8import tools.refinery.store.representation.TruthValueDomain;
9
10import java.util.Arrays;
11import java.util.List;
12
13public record PartialRelation(String name, int arity) implements PartialSymbol<TruthValue, Boolean>, Constraint {
14 @Override
15 public AbstractDomain<TruthValue, Boolean> abstractDomain() {
16 return TruthValueDomain.INSTANCE;
17 }
18
19 @Override
20 public TruthValue defaultValue() {
21 return TruthValue.FALSE;
22 }
23
24 @Override
25 public Boolean defaultConcreteValue() {
26 return false;
27 }
28
29 @Override
30 public List<Sort> getSorts() {
31 var sorts = new Sort[arity()];
32 Arrays.fill(sorts, NodeSort.INSTANCE);
33 return List.of(sorts);
34 }
35
36 @Override
37 public String toReferenceString() {
38 return name;
39 }
40
41 @Override
42 public boolean equals(Object o) {
43 return this == o;
44 }
45
46 @Override
47 public int hashCode() {
48 // Compare by identity to make hash table lookups more efficient.
49 return System.identityHashCode(this);
50 }
51
52 @Override
53 public String toString() {
54 return "%s/%d".formatted(name, arity);
55 }
56}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java
new file mode 100644
index 00000000..1af11f2e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/representation/PartialSymbol.java
@@ -0,0 +1,12 @@
1package tools.refinery.store.reasoning.representation;
2
3import tools.refinery.store.representation.AbstractDomain;
4
5public sealed interface PartialSymbol<A, C> extends AnyPartialSymbol permits PartialFunction, PartialRelation {
6 @Override
7 AbstractDomain<A, C> abstractDomain();
8
9 A defaultValue();
10
11 C defaultConcreteValue();
12}
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
new file mode 100644
index 00000000..e8ed05a3
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RelationRefinementAction.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.reasoning.rule;
2
3import tools.refinery.store.reasoning.Reasoning;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5import tools.refinery.store.model.Model;
6import tools.refinery.store.query.term.Variable;
7import tools.refinery.store.representation.TruthValue;
8import tools.refinery.store.tuple.Tuple;
9
10import java.util.List;
11
12public record RelationRefinementAction(PartialRelation target, List<Variable> arguments, TruthValue value)
13 implements RuleAction {
14 public RelationRefinementAction {
15 if (arguments.size() != target.arity()) {
16 throw new IllegalArgumentException("%s needs %d parameters, but got %s".formatted(target.name(),
17 target.arity(), arguments.size()));
18 }
19 if (value == TruthValue.UNKNOWN) {
20 throw new IllegalArgumentException("Refining with UNKNOWN has no effect");
21 }
22 }
23
24 @Override
25 public RuleActionExecutor createExecutor(int[] argumentIndices, Model model) {
26 var targetInterpretation = model.getAdapter(Reasoning.ADAPTER).getPartialInterpretation(target);
27 return activationTuple -> {
28 int arity = argumentIndices.length;
29 var arguments = new int[arity];
30 for (int i = 0; i < arity; i++) {
31 arguments[i] = activationTuple.get(argumentIndices[i]);
32 }
33 return targetInterpretation.merge(Tuple.of(arguments), value);
34 };
35 }
36}
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
new file mode 100644
index 00000000..c7b16d47
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/Rule.java
@@ -0,0 +1,38 @@
1package tools.refinery.store.reasoning.rule;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.dnf.Dnf;
5
6import java.util.ArrayList;
7import java.util.HashSet;
8import java.util.List;
9
10public record Rule(Dnf precondition, List<RuleAction> actions) {
11 public Rule {
12 var parameterSet = new HashSet<>(precondition.getParameters());
13 for (var action : actions) {
14 for (var argument : action.arguments()) {
15 if (!parameterSet.contains(argument)) {
16 throw new IllegalArgumentException(
17 "Argument %s of action %s does not appear in the parameter list %s of %s"
18 .formatted(argument, action, precondition.getParameters(), precondition.name()));
19 }
20 }
21 }
22 }
23
24 public RuleExecutor createExecutor(Model model) {
25 var parameters = precondition.getParameters();
26 var actionExecutors = new ArrayList<RuleActionExecutor>(actions.size());
27 for (var action : actions) {
28 var arguments = action.arguments();
29 int arity = arguments.size();
30 var argumentIndices = new int[arity];
31 for (int i = 0; i < arity; i++) {
32 argumentIndices[i] = parameters.indexOf(arguments.get(i));
33 }
34 actionExecutors.add(action.createExecutor(argumentIndices, model));
35 }
36 return new RuleExecutor(this, model, actionExecutors);
37 }
38}
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
new file mode 100644
index 00000000..4753b8bc
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleAction.java
@@ -0,0 +1,12 @@
1package tools.refinery.store.reasoning.rule;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.term.Variable;
5
6import java.util.List;
7
8public interface RuleAction {
9 List<Variable> arguments();
10
11 RuleActionExecutor createExecutor(int[] argumentIndices, Model model);
12}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleActionExecutor.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleActionExecutor.java
new file mode 100644
index 00000000..80bfa6f8
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleActionExecutor.java
@@ -0,0 +1,9 @@
1package tools.refinery.store.reasoning.rule;
2
3import tools.refinery.store.reasoning.MergeResult;
4import tools.refinery.store.tuple.TupleLike;
5
6@FunctionalInterface
7public interface RuleActionExecutor {
8 MergeResult execute(TupleLike activationTuple);
9}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleExecutor.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleExecutor.java
new file mode 100644
index 00000000..1e5322b4
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/rule/RuleExecutor.java
@@ -0,0 +1,34 @@
1package tools.refinery.store.reasoning.rule;
2
3import tools.refinery.store.reasoning.MergeResult;
4import tools.refinery.store.model.Model;
5import tools.refinery.store.tuple.TupleLike;
6
7import java.util.List;
8
9public final class RuleExecutor {
10 private final Rule rule;
11 private final Model model;
12 private final List<RuleActionExecutor> actionExecutors;
13
14 RuleExecutor(Rule rule, Model model, List<RuleActionExecutor> actionExecutors) {
15 this.rule = rule;
16 this.model = model;
17 this.actionExecutors = actionExecutors;
18 }
19
20 public Rule getRule() {
21 return rule;
22 }
23
24 public Model getModel() {
25 return model;
26 }
27 public MergeResult execute(TupleLike activationTuple) {
28 MergeResult mergeResult = MergeResult.UNCHANGED;
29 for (var actionExecutor : actionExecutors) {
30 mergeResult = mergeResult.andAlso(actionExecutor.execute(activationTuple));
31 }
32 return mergeResult;
33 }
34}
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
new file mode 100644
index 00000000..90633495
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/seed/Seed.java
@@ -0,0 +1,14 @@
1package tools.refinery.store.reasoning.seed;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.tuple.Tuple;
5
6public interface Seed<T> {
7 int arity();
8
9 T reducedValue();
10
11 T get(Tuple key);
12
13 Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount);
14}
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 @@
1package tools.refinery.store.reasoning.seed;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.tuple.Tuple;
5
6public record UniformSeed<T>(int arity, T reducedValue) implements Seed<T> {
7 public UniformSeed {
8 if (arity < 0) {
9 throw new IllegalArgumentException("Arity must not be negative");
10 }
11 }
12
13 @Override
14 public T get(Tuple key) {
15 return reducedValue;
16 }
17
18 @Override
19 public Cursor<Tuple, T> getCursor(T defaultValue, int nodeCount) {
20 return null;
21 }
22}
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
new file mode 100644
index 00000000..5cdfedf7
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/Advice.java
@@ -0,0 +1,159 @@
1package tools.refinery.store.reasoning.translator;
2
3import tools.refinery.store.reasoning.representation.AnyPartialSymbol;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5import tools.refinery.store.query.term.Variable;
6import tools.refinery.store.query.literal.Literal;
7import tools.refinery.store.query.substitution.Substitutions;
8
9import java.util.*;
10
11public final class Advice {
12 private final AnyPartialSymbol source;
13 private final PartialRelation target;
14 private final AdviceSlot slot;
15 private final boolean mandatory;
16 private final List<Variable> parameters;
17 private final List<Literal> literals;
18 private boolean processed;
19
20 public Advice(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot, boolean mandatory, List<Variable> parameters, List<Literal> literals) {
21 if (mandatory && !slot.isMonotonic()) {
22 throw new IllegalArgumentException("Only monotonic advice can be mandatory");
23 }
24 this.source = source;
25 this.target = target;
26 this.slot = slot;
27 this.mandatory = mandatory;
28 checkArity(parameters);
29 this.parameters = parameters;
30 this.literals = literals;
31 }
32
33 public AnyPartialSymbol source() {
34 return source;
35 }
36
37 public PartialRelation target() {
38 return target;
39 }
40
41 public AdviceSlot slot() {
42 return slot;
43 }
44
45 public boolean mandatory() {
46 return mandatory;
47 }
48
49 public List<Variable> parameters() {
50 return parameters;
51 }
52
53 public List<Literal> literals() {
54 return literals;
55 }
56
57 public boolean processed() {
58 return processed;
59 }
60
61 public List<Literal> substitute(List<Variable> substituteParameters) {
62 checkArity(substituteParameters);
63 markProcessed();
64 int arity = parameters.size();
65 var variableMap = new HashMap<Variable, Variable>(arity);
66 for (int i = 0; i < arity; i++) {
67 variableMap.put(parameters.get(i), substituteParameters.get(i));
68 }
69 // Use a renewing substitution to remove any non-parameter variables and avoid clashed between variables
70 // coming from different advice in the same clause.
71 var substitution = Substitutions.renewing(variableMap);
72 return literals.stream().map(literal -> literal.substitute(substitution)).toList();
73 }
74
75 private void markProcessed() {
76 processed = true;
77 }
78
79 public void checkProcessed() {
80 if (mandatory && !processed) {
81 throw new IllegalStateException("Mandatory advice %s was not processed".formatted(this));
82 }
83 }
84
85 private void checkArity(List<Variable> toCheck) {
86 if (toCheck.size() != target.arity()) {
87 throw new IllegalArgumentException("%s needs %d parameters, but got %s".formatted(target.name(),
88 target.arity(), parameters.size()));
89 }
90 }
91
92 public static Builder builderFor(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) {
93 return new Builder(source, target, slot);
94 }
95
96
97 @Override
98 public String toString() {
99 return "Advice[source=%s, target=%s, slot=%s, mandatory=%s, parameters=%s, literals=%s]".formatted(source,
100 target, slot, mandatory, parameters, literals);
101 }
102
103 public static class Builder {
104 private final AnyPartialSymbol source;
105 private final PartialRelation target;
106 private final AdviceSlot slot;
107 private boolean mandatory;
108 private final List<Variable> parameters = new ArrayList<>();
109 private final List<Literal> literals = new ArrayList<>();
110
111 private Builder(AnyPartialSymbol source, PartialRelation target, AdviceSlot slot) {
112 this.source = source;
113 this.target = target;
114 this.slot = slot;
115 }
116
117 public Builder mandatory(boolean mandatory) {
118 this.mandatory = mandatory;
119 return this;
120 }
121
122 public Builder mandatory() {
123 return mandatory(false);
124 }
125
126 public Builder parameters(List<Variable> variables) {
127 parameters.addAll(variables);
128 return this;
129 }
130
131 public Builder parameters(Variable... variables) {
132 return parameters(List.of(variables));
133 }
134
135 public Builder parameter(Variable variable) {
136 parameters.add(variable);
137 return this;
138 }
139
140 public Builder literals(Collection<Literal> literals) {
141 this.literals.addAll(literals);
142 return this;
143 }
144
145 public Builder literals(Literal... literals) {
146 return literals(List.of(literals));
147 }
148
149 public Builder literal(Literal literal) {
150 literals.add(literal);
151 return this;
152 }
153
154 public Advice build() {
155 return new Advice(source, target, slot, mandatory, Collections.unmodifiableList(parameters),
156 Collections.unmodifiableList(literals));
157 }
158 }
159}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java
new file mode 100644
index 00000000..f3bd9c5e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/AdviceSlot.java
@@ -0,0 +1,25 @@
1package tools.refinery.store.reasoning.translator;
2
3import tools.refinery.store.representation.TruthValue;
4
5public enum AdviceSlot {
6 EXTEND_MUST(true),
7
8 RESTRICT_MAY(true),
9
10 /**
11 * Same as {@link #RESTRICT_MAY}, but only active if the value of the relation is not {@link TruthValue#TRUE} or
12 * {@link TruthValue#ERROR}.
13 */
14 RESTRICT_NEW(false);
15
16 private final boolean monotonic;
17
18 AdviceSlot(boolean monotonic) {
19 this.monotonic = monotonic;
20 }
21
22 public boolean isMonotonic() {
23 return monotonic;
24 }
25}
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 @@
1package tools.refinery.store.reasoning.translator;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.term.Variable;
5import tools.refinery.store.query.literal.CallPolarity;
6import tools.refinery.store.query.literal.Literal;
7import tools.refinery.store.reasoning.PartialInterpretation;
8import tools.refinery.store.reasoning.literal.Modality;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.TruthValue;
11
12import java.util.List;
13
14public interface TranslatedRelation {
15 PartialRelation getSource();
16
17 void configure(List<Advice> advices);
18
19 List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments);
20
21 PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model);
22}
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
new file mode 100644
index 00000000..24b93911
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/TranslationUnit.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.reasoning.translator;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.model.ModelStoreBuilder;
5import tools.refinery.store.reasoning.ReasoningBuilder;
6
7import java.util.Collection;
8
9public abstract class TranslationUnit {
10 private ReasoningBuilder reasoningBuilder;
11
12 protected ReasoningBuilder getReasoningBuilder() {
13 return reasoningBuilder;
14 }
15
16 public void setPartialInterpretationBuilder(ReasoningBuilder reasoningBuilder) {
17 this.reasoningBuilder = reasoningBuilder;
18 configureReasoningBuilder();
19 }
20
21 protected ModelStoreBuilder getModelStoreBuilder() {
22 return reasoningBuilder.getStoreBuilder();
23 }
24
25 protected void configureReasoningBuilder() {
26 // Nothing to configure by default.
27 }
28
29 public abstract Collection<TranslatedRelation> getTranslatedRelations();
30
31 public abstract void initializeModel(Model model, int nodeCount);
32}
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 @@
1package tools.refinery.store.reasoning.translator.base;
2
3import tools.refinery.store.map.Cursor;
4import tools.refinery.store.model.Interpretation;
5import tools.refinery.store.query.ResultSet;
6import tools.refinery.store.reasoning.MergeResult;
7import tools.refinery.store.reasoning.PartialInterpretation;
8import tools.refinery.store.reasoning.ReasoningAdapter;
9import tools.refinery.store.reasoning.representation.PartialRelation;
10import tools.refinery.store.representation.TruthValue;
11import tools.refinery.store.tuple.Tuple;
12
13public class BaseDecisionInterpretation implements PartialInterpretation<TruthValue, Boolean> {
14 private final ReasoningAdapter reasoningAdapter;
15 private PartialRelation partialRelation;
16 private final ResultSet<Boolean> mustResultSet;
17 private final ResultSet<Boolean> mayResultSet;
18 private final ResultSet<Boolean> errorResultSet;
19 private final ResultSet<Boolean> currentResultSet;
20 private final Interpretation<TruthValue> interpretation;
21
22 public BaseDecisionInterpretation(ReasoningAdapter reasoningAdapter, ResultSet<Boolean> mustResultSet,
23 ResultSet<Boolean> mayResultSet, ResultSet<Boolean> errorResultSet,
24 ResultSet<Boolean> currentResultSet, Interpretation<TruthValue> interpretation) {
25 this.reasoningAdapter = reasoningAdapter;
26 this.mustResultSet = mustResultSet;
27 this.mayResultSet = mayResultSet;
28 this.errorResultSet = errorResultSet;
29 this.currentResultSet = currentResultSet;
30 this.interpretation = interpretation;
31 }
32
33 @Override
34 public ReasoningAdapter getAdapter() {
35 return reasoningAdapter;
36 }
37
38 @Override
39 public int countUnfinished() {
40 return 0;
41 }
42
43 @Override
44 public int countErrors() {
45 return errorResultSet.size();
46 }
47
48 @Override
49 public PartialRelation getPartialSymbol() {
50 return partialRelation;
51 }
52
53 @Override
54 public TruthValue get(Tuple key) {
55 return null;
56 }
57
58 @Override
59 public Cursor<Tuple, TruthValue> getAll() {
60 return null;
61 }
62
63 @Override
64 public MergeResult merge(Tuple key, TruthValue value) {
65 TruthValue newValue;
66 switch (value) {
67 case UNKNOWN -> {
68 return MergeResult.UNCHANGED;
69 }
70 case TRUE -> newValue = mayResultSet.get(key) ? TruthValue.TRUE : TruthValue.ERROR;
71 case FALSE -> newValue = mustResultSet.get(key) ? TruthValue.ERROR : TruthValue.FALSE;
72 case ERROR -> newValue = TruthValue.ERROR;
73 default -> throw new IllegalArgumentException("Unknown truth value: " + value);
74 }
75 var oldValue = interpretation.put(key, newValue);
76 return oldValue == TruthValue.ERROR ? MergeResult.UNCHANGED : MergeResult.REFINED;
77 }
78
79 @Override
80 public Boolean getConcrete(Tuple key) {
81 return currentResultSet.get(key);
82 }
83
84 @Override
85 public Cursor<Tuple, Boolean> getAllConcrete() {
86 return null;
87 }
88}
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 @@
1package tools.refinery.store.reasoning.translator.base;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5import tools.refinery.store.reasoning.seed.Seed;
6import tools.refinery.store.reasoning.seed.UniformSeed;
7import tools.refinery.store.reasoning.translator.TranslatedRelation;
8import tools.refinery.store.reasoning.translator.TranslationUnit;
9import tools.refinery.store.representation.Symbol;
10import tools.refinery.store.representation.TruthValue;
11
12import java.util.Collection;
13import java.util.List;
14
15public class BaseDecisionTranslationUnit extends TranslationUnit {
16 private final PartialRelation partialRelation;
17 private final Seed<TruthValue> seed;
18 private final Symbol<TruthValue> symbol;
19
20 public BaseDecisionTranslationUnit(PartialRelation partialRelation, Seed<TruthValue> seed) {
21 if (seed.arity() != partialRelation.arity()) {
22 throw new IllegalArgumentException("Expected seed with arity %d for %s, got arity %s"
23 .formatted(partialRelation.arity(), partialRelation, seed.arity()));
24 }
25 this.partialRelation = partialRelation;
26 this.seed = seed;
27 symbol = new Symbol<>(partialRelation.name(), partialRelation.arity(), TruthValue.class, TruthValue.UNKNOWN);
28 }
29
30 public BaseDecisionTranslationUnit(PartialRelation partialRelation) {
31 this(partialRelation, new UniformSeed<>(partialRelation.arity(), TruthValue.UNKNOWN));
32 }
33
34 @Override
35 protected void configureReasoningBuilder() {
36 getModelStoreBuilder().symbol(symbol);
37 }
38
39 @Override
40 public Collection<TranslatedRelation> getTranslatedRelations() {
41 return List.of(new TranslatedBaseDecision(getReasoningBuilder(), partialRelation, symbol));
42 }
43
44 @Override
45 public void initializeModel(Model model, int nodeCount) {
46 var interpretation = model.getInterpretation(symbol);
47 interpretation.putAll(seed.getCursor(TruthValue.UNKNOWN, nodeCount));
48 }
49}
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 @@
1package tools.refinery.store.reasoning.translator.base;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.query.term.Variable;
5import tools.refinery.store.query.literal.CallPolarity;
6import tools.refinery.store.query.literal.Literal;
7import tools.refinery.store.reasoning.PartialInterpretation;
8import tools.refinery.store.reasoning.ReasoningBuilder;
9import tools.refinery.store.reasoning.literal.Modality;
10import tools.refinery.store.reasoning.representation.PartialRelation;
11import tools.refinery.store.reasoning.translator.Advice;
12import tools.refinery.store.reasoning.translator.TranslatedRelation;
13import tools.refinery.store.representation.Symbol;
14import tools.refinery.store.representation.TruthValue;
15
16import java.util.List;
17
18class TranslatedBaseDecision implements TranslatedRelation {
19 private final ReasoningBuilder reasoningBuilder;
20 private final PartialRelation partialRelation;
21 private final Symbol<TruthValue> symbol;
22
23 public TranslatedBaseDecision(ReasoningBuilder reasoningBuilder, PartialRelation partialRelation,
24 Symbol<TruthValue> symbol) {
25 this.reasoningBuilder = reasoningBuilder;
26 this.partialRelation = partialRelation;
27 this.symbol = symbol;
28 }
29
30 @Override
31 public PartialRelation getSource() {
32 return partialRelation;
33 }
34
35 @Override
36 public void configure(List<Advice> advices) {
37
38 }
39
40 @Override
41 public List<Literal> call(CallPolarity polarity, Modality modality, List<Variable> arguments) {
42 return null;
43 }
44
45 @Override
46 public PartialInterpretation<TruthValue, Boolean> createPartialInterpretation(Model model) {
47 return null;
48 }
49}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java
new file mode 100644
index 00000000..1b8d7cc9
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/EliminatedType.java
@@ -0,0 +1,6 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4
5record EliminatedType(PartialRelation replacement) implements TypeAnalysisResult {
6}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java
new file mode 100644
index 00000000..43b8e1dd
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/ExtendedTypeInfo.java
@@ -0,0 +1,101 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import org.jetbrains.annotations.NotNull;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5
6import java.util.HashSet;
7import java.util.LinkedHashSet;
8import java.util.Objects;
9import java.util.Set;
10
11final class ExtendedTypeInfo implements Comparable<ExtendedTypeInfo> {
12 private final int index;
13 private final PartialRelation type;
14 private final TypeInfo typeInfo;
15 private final Set<PartialRelation> allSubtypes = new LinkedHashSet<>();
16 private final Set<PartialRelation> allSupertypes;
17 private final Set<PartialRelation> concreteSubtypesAndSelf = new LinkedHashSet<>();
18 private Set<PartialRelation> directSubtypes;
19 private final Set<PartialRelation> unsortedDirectSupertypes = new HashSet<>();
20
21 public ExtendedTypeInfo(int index, PartialRelation type, TypeInfo typeInfo) {
22 this.index = index;
23 this.type = type;
24 this.typeInfo = typeInfo;
25 this.allSupertypes = new LinkedHashSet<>(typeInfo.supertypes());
26 }
27
28 public PartialRelation getType() {
29 return type;
30 }
31
32 public TypeInfo getTypeInfo() {
33 return typeInfo;
34 }
35
36 public boolean isAbstractType() {
37 return getTypeInfo().abstractType();
38 }
39
40 public Set<PartialRelation> getAllSubtypes() {
41 return allSubtypes;
42 }
43
44 public Set<PartialRelation> getAllSupertypes() {
45 return allSupertypes;
46 }
47
48 public Set<PartialRelation> getAllSupertypesAndSelf() {
49 var allSubtypesAndSelf = new HashSet<PartialRelation>(allSupertypes.size() + 1);
50 addMust(allSubtypesAndSelf);
51 return allSubtypesAndSelf;
52 }
53
54 public Set<PartialRelation> getConcreteSubtypesAndSelf() {
55 return concreteSubtypesAndSelf;
56 }
57
58 public Set<PartialRelation> getDirectSubtypes() {
59 return directSubtypes;
60 }
61
62 public Set<PartialRelation> getUnsortedDirectSupertypes() {
63 return unsortedDirectSupertypes;
64 }
65
66 public void setDirectSubtypes(Set<PartialRelation> directSubtypes) {
67 this.directSubtypes = directSubtypes;
68 }
69
70 public boolean allowsAllConcreteTypes(Set<PartialRelation> concreteTypes) {
71 for (var concreteType : concreteTypes) {
72 if (!concreteSubtypesAndSelf.contains(concreteType)) {
73 return false;
74 }
75 }
76 return true;
77 }
78
79 public void addMust(Set<PartialRelation> mustTypes) {
80 mustTypes.add(type);
81 mustTypes.addAll(allSupertypes);
82 }
83
84 @Override
85 public int compareTo(@NotNull ExtendedTypeInfo extendedTypeInfo) {
86 return Integer.compare(index, extendedTypeInfo.index);
87 }
88
89 @Override
90 public boolean equals(Object o) {
91 if (this == o) return true;
92 if (o == null || getClass() != o.getClass()) return false;
93 ExtendedTypeInfo that = (ExtendedTypeInfo) o;
94 return index == that.index;
95 }
96
97 @Override
98 public int hashCode() {
99 return Objects.hash(index);
100 }
101}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeRelationView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeRelationView.java
new file mode 100644
index 00000000..12c37c86
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMayTypeRelationView.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4import tools.refinery.store.query.view.TuplePreservingRelationView;
5import tools.refinery.store.tuple.Tuple;
6
7class InferredMayTypeRelationView extends TuplePreservingRelationView<InferredType> {
8 private final PartialRelation type;
9
10 InferredMayTypeRelationView(PartialRelation type) {
11 super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#may".formatted(type));
12 this.type = type;
13 }
14
15 @Override
16 public boolean filter(Tuple key, InferredType value) {
17 return value.mayConcreteTypes().contains(type);
18 }
19}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeRelationView.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeRelationView.java
new file mode 100644
index 00000000..975f627e
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredMustTypeRelationView.java
@@ -0,0 +1,19 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4import tools.refinery.store.query.view.TuplePreservingRelationView;
5import tools.refinery.store.tuple.Tuple;
6
7class InferredMustTypeRelationView extends TuplePreservingRelationView<InferredType> {
8 private final PartialRelation type;
9
10 InferredMustTypeRelationView(PartialRelation type) {
11 super(TypeHierarchyTranslationUnit.INFERRED_TYPE_SYMBOL, "%s#must".formatted(type));
12 this.type = type;
13 }
14
15 @Override
16 public boolean filter(Tuple key, InferredType value) {
17 return value.mustTypes().contains(type);
18 }
19}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java
new file mode 100644
index 00000000..a366e262
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredType.java
@@ -0,0 +1,30 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4
5import java.util.Collections;
6import java.util.Set;
7
8record InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes,
9 PartialRelation currentType) {
10 public static final InferredType UNTYPED = new InferredType(Set.of(), Set.of(), null);
11
12 public InferredType(Set<PartialRelation> mustTypes, Set<PartialRelation> mayConcreteTypes,
13 PartialRelation currentType) {
14 this.mustTypes = Collections.unmodifiableSet(mustTypes);
15 this.mayConcreteTypes = Collections.unmodifiableSet(mayConcreteTypes);
16 this.currentType = currentType;
17 }
18
19 public boolean isConsistent() {
20 return currentType != null || mustTypes.isEmpty();
21 }
22
23 public boolean isMust(PartialRelation partialRelation) {
24 return mustTypes.contains(partialRelation);
25 }
26
27 public boolean isMayConcrete(PartialRelation partialRelation) {
28 return mayConcreteTypes.contains(partialRelation);
29 }
30}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java
new file mode 100644
index 00000000..63dba964
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/PreservedType.java
@@ -0,0 +1,136 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4import tools.refinery.store.representation.TruthValue;
5
6import java.util.*;
7
8final class PreservedType implements TypeAnalysisResult {
9 private final ExtendedTypeInfo extendedTypeInfo;
10 private final List<PartialRelation> directSubtypes;
11 private final List<ExtendedTypeInfo> allExternalTypeInfoList;
12 private final InferredType inferredType;
13
14 public PreservedType(ExtendedTypeInfo extendedTypeInfo, List<ExtendedTypeInfo> allExternalTypeInfoList) {
15 this.extendedTypeInfo = extendedTypeInfo;
16 directSubtypes = List.copyOf(extendedTypeInfo.getDirectSubtypes());
17 this.allExternalTypeInfoList = allExternalTypeInfoList;
18 inferredType = propagateMust(extendedTypeInfo.getAllSupertypesAndSelf(),
19 extendedTypeInfo.getConcreteSubtypesAndSelf());
20 }
21
22 public PartialRelation type() {
23 return extendedTypeInfo.getType();
24 }
25
26 public List<PartialRelation> getDirectSubtypes() {
27 return directSubtypes;
28 }
29
30 public boolean isAbstractType() {
31 return extendedTypeInfo.isAbstractType();
32 }
33
34 public boolean isVacuous() {
35 return isAbstractType() && directSubtypes.isEmpty();
36 }
37
38 public InferredType asInferredType() {
39 return inferredType;
40 }
41
42 public InferredType merge(InferredType inferredType, TruthValue value) {
43 return switch (value) {
44 case UNKNOWN -> inferredType;
45 case TRUE -> addMust(inferredType);
46 case FALSE -> removeMay(inferredType);
47 case ERROR -> addError(inferredType);
48 };
49 }
50
51 private InferredType addMust(InferredType inferredType) {
52 var originalMustTypes = inferredType.mustTypes();
53 if (originalMustTypes.contains(type())) {
54 return inferredType;
55 }
56 var mustTypes = new HashSet<>(originalMustTypes);
57 extendedTypeInfo.addMust(mustTypes);
58 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
59 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
60 Set<PartialRelation> mayConcreteTypesResult;
61 if (mayConcreteTypes.retainAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
62 mayConcreteTypesResult = mayConcreteTypes;
63 } else {
64 mayConcreteTypesResult = originalMayConcreteTypes;
65 }
66 return propagateMust(mustTypes, mayConcreteTypesResult);
67 }
68
69 private InferredType removeMay(InferredType inferredType) {
70 var originalMayConcreteTypes = inferredType.mayConcreteTypes();
71 var mayConcreteTypes = new LinkedHashSet<>(originalMayConcreteTypes);
72 if (!mayConcreteTypes.removeAll(extendedTypeInfo.getConcreteSubtypesAndSelf())) {
73 return inferredType;
74 }
75 return propagateMust(inferredType.mustTypes(), mayConcreteTypes);
76 }
77
78 private InferredType addError(InferredType inferredType) {
79 var originalMustTypes = inferredType.mustTypes();
80 if (originalMustTypes.contains(type())) {
81 if (inferredType.mayConcreteTypes().isEmpty()) {
82 return inferredType;
83 }
84 return new InferredType(originalMustTypes, Set.of(), null);
85 }
86 var mustTypes = new HashSet<>(originalMustTypes);
87 extendedTypeInfo.addMust(mustTypes);
88 return new InferredType(mustTypes, Set.of(), null);
89 }
90
91 private InferredType propagateMust(Set<PartialRelation> originalMustTypes,
92 Set<PartialRelation> mayConcreteTypes) {
93 // It is possible that there is not type at all, do not force one by propagation.
94 var maybeUntyped = originalMustTypes.isEmpty();
95 // Para-consistent case, do not propagate must types to avoid logical explosion.
96 var paraConsistentOrSurelyUntyped = mayConcreteTypes.isEmpty();
97 if (maybeUntyped || paraConsistentOrSurelyUntyped) {
98 return new InferredType(originalMustTypes, mayConcreteTypes, null);
99 }
100 var currentType = computeCurrentType(mayConcreteTypes);
101 var mustTypes = new HashSet<>(originalMustTypes);
102 boolean changed = false;
103 for (var newMustExtendedTypeInfo : allExternalTypeInfoList) {
104 var newMustType = newMustExtendedTypeInfo.getType();
105 if (mustTypes.contains(newMustType)) {
106 continue;
107 }
108 if (newMustExtendedTypeInfo.allowsAllConcreteTypes(mayConcreteTypes)) {
109 newMustExtendedTypeInfo.addMust(mustTypes);
110 changed = true;
111 }
112 }
113 if (!changed) {
114 return new InferredType(originalMustTypes, mayConcreteTypes, currentType);
115 }
116 return new InferredType(mustTypes, mayConcreteTypes, currentType);
117 }
118
119 /**
120 * Returns a concrete type that is allowed by a (consistent, i.e., nonempty) set of <b>may</b> concrete types.
121 *
122 * @param mayConcreteTypes The set of allowed concrete types. Must not be empty.
123 * @return The first concrete type that is allowed by {@code matConcreteTypes}.
124 */
125 private PartialRelation computeCurrentType(Set<PartialRelation> mayConcreteTypes) {
126 for (var concreteExtendedTypeInfo : allExternalTypeInfoList) {
127 var concreteType = concreteExtendedTypeInfo.getType();
128 if (!concreteExtendedTypeInfo.isAbstractType() && mayConcreteTypes.contains(concreteType)) {
129 return concreteType;
130 }
131 }
132 // We have already filtered out the para-consistent case in {@link #propagateMust(Set<PartialRelation>,
133 // Set<PartialRelation>}.
134 throw new AssertionError("No concrete type in %s".formatted(mayConcreteTypes));
135 }
136}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java
new file mode 100644
index 00000000..4f915108
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalysisResult.java
@@ -0,0 +1,4 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3sealed interface TypeAnalysisResult permits EliminatedType, PreservedType {
4}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java
new file mode 100644
index 00000000..62f8e750
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzer.java
@@ -0,0 +1,202 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4
5import java.util.*;
6
7class TypeAnalyzer {
8 private final Map<PartialRelation, ExtendedTypeInfo> extendedTypeInfoMap;
9 private final Map<PartialRelation, PartialRelation> replacements = new LinkedHashMap<>();
10 private final InferredType unknownType;
11 private final Map<PartialRelation, TypeAnalysisResult> analysisResults;
12
13 public TypeAnalyzer(Map<PartialRelation, TypeInfo> typeInfoMap) {
14 int size = typeInfoMap.size();
15 extendedTypeInfoMap = new LinkedHashMap<>(size);
16 var concreteTypes = new LinkedHashSet<PartialRelation>();
17 int index = 0;
18 for (var entry : typeInfoMap.entrySet()) {
19 var type = entry.getKey();
20 var typeInfo = entry.getValue();
21 extendedTypeInfoMap.put(type, new ExtendedTypeInfo(index, type, typeInfo));
22 if (!typeInfo.abstractType()) {
23 concreteTypes.add(type);
24 }
25 index++;
26 }
27 unknownType = new InferredType(Set.of(), concreteTypes, null);
28 computeAllSupertypes();
29 computeAllAndConcreteSubtypes();
30 computeDirectSubtypes();
31 eliminateTrivialSupertypes();
32 analysisResults = computeAnalysisResults();
33 }
34
35 public InferredType getUnknownType() {
36 return unknownType;
37 }
38
39 public Map<PartialRelation, TypeAnalysisResult> getAnalysisResults() {
40 return analysisResults;
41 }
42
43 private void computeAllSupertypes() {
44 boolean changed;
45 do {
46 changed = false;
47 for (var extendedTypeInfo : extendedTypeInfoMap.values()) {
48 var found = new HashSet<PartialRelation>();
49 var allSupertypes = extendedTypeInfo.getAllSupertypes();
50 for (var supertype : allSupertypes) {
51 found.addAll(extendedTypeInfoMap.get(supertype).getAllSupertypes());
52 }
53 if (allSupertypes.addAll(found)) {
54 changed = true;
55 }
56 }
57 } while (changed);
58 }
59
60 private void computeAllAndConcreteSubtypes() {
61 for (var extendedTypeInfo : extendedTypeInfoMap.values()) {
62 var type = extendedTypeInfo.getType();
63 if (!extendedTypeInfo.isAbstractType()) {
64 extendedTypeInfo.getConcreteSubtypesAndSelf().add(type);
65 }
66 for (var supertype : extendedTypeInfo.getAllSupertypes()) {
67 if (type.equals(supertype)) {
68 throw new IllegalArgumentException("%s cannot be a supertype of itself".formatted(type));
69 }
70 var supertypeInfo = extendedTypeInfoMap.get(supertype);
71 supertypeInfo.getAllSubtypes().add(type);
72 if (!extendedTypeInfo.isAbstractType()) {
73 supertypeInfo.getConcreteSubtypesAndSelf().add(type);
74 }
75 }
76 }
77 }
78
79 private void computeDirectSubtypes() {
80 for (var extendedTypeInfo : extendedTypeInfoMap.values()) {
81 var allSubtypes = extendedTypeInfo.getAllSubtypes();
82 var directSubtypes = new LinkedHashSet<>(allSubtypes);
83 var indirectSubtypes = new LinkedHashSet<PartialRelation>(allSubtypes.size());
84 for (var subtype : allSubtypes) {
85 indirectSubtypes.addAll(extendedTypeInfoMap.get(subtype).getAllSubtypes());
86 }
87 directSubtypes.removeAll(indirectSubtypes);
88 extendedTypeInfo.setDirectSubtypes(directSubtypes);
89 }
90 }
91
92 private void eliminateTrivialSupertypes() {
93 boolean changed;
94 do {
95 var toRemove = new ArrayList<PartialRelation>();
96 for (var entry : extendedTypeInfoMap.entrySet()) {
97 var extendedTypeInfo = entry.getValue();
98 boolean isAbstract = extendedTypeInfo.isAbstractType();
99 // Do not eliminate abstract types with 0 subtypes, because they can be used para-consistently, i.e.,
100 // an object determined to <b>must</b> have an abstract type with 0 subtypes <b>may not</b> ever exist.
101 boolean hasSingleDirectSubtype = extendedTypeInfo.getDirectSubtypes().size() == 1;
102 if (isAbstract && hasSingleDirectSubtype) {
103 toRemove.add(entry.getKey());
104 }
105 }
106 toRemove.forEach(this::removeTrivialType);
107 changed = !toRemove.isEmpty();
108 } while (changed);
109 }
110
111 private void removeTrivialType(PartialRelation trivialType) {
112 var extendedTypeInfo = extendedTypeInfoMap.get(trivialType);
113 var iterator = extendedTypeInfo.getDirectSubtypes().iterator();
114 if (!iterator.hasNext()) {
115 throw new AssertionError("Expected trivial supertype %s to have a direct subtype"
116 .formatted(trivialType));
117 }
118 PartialRelation replacement = setReplacement(trivialType, iterator.next());
119 if (iterator.hasNext()) {
120 throw new AssertionError("Expected trivial supertype %s to have at most 1 direct subtype"
121 .formatted(trivialType));
122 }
123 replacements.put(trivialType, replacement);
124 for (var supertype : extendedTypeInfo.getAllSupertypes()) {
125 var extendedSupertypeInfo = extendedTypeInfoMap.get(supertype);
126 if (!extendedSupertypeInfo.getAllSubtypes().remove(trivialType)) {
127 throw new AssertionError("Expected %s to be subtype of %s".formatted(trivialType, supertype));
128 }
129 var directSubtypes = extendedSupertypeInfo.getDirectSubtypes();
130 if (directSubtypes.remove(trivialType)) {
131 directSubtypes.add(replacement);
132 }
133 }
134 for (var subtype : extendedTypeInfo.getAllSubtypes()) {
135 var extendedSubtypeInfo = extendedTypeInfoMap.get(subtype);
136 if (!extendedSubtypeInfo.getAllSupertypes().remove(trivialType)) {
137 throw new AssertionError("Expected %s to be supertype of %s".formatted(trivialType, subtype));
138 }
139 }
140 extendedTypeInfoMap.remove(trivialType);
141 }
142
143 private PartialRelation setReplacement(PartialRelation trivialRelation, PartialRelation replacement) {
144 if (replacement == null) {
145 return trivialRelation;
146 }
147 var resolved = setReplacement(replacement, replacements.get(replacement));
148 replacements.put(trivialRelation, resolved);
149 return resolved;
150 }
151
152 private Map<PartialRelation, TypeAnalysisResult> computeAnalysisResults() {
153 var allExtendedTypeInfoList = sortTypes();
154 var results = new LinkedHashMap<PartialRelation, TypeAnalysisResult>(
155 allExtendedTypeInfoList.size() + replacements.size());
156 for (var extendedTypeInfo : allExtendedTypeInfoList) {
157 var type = extendedTypeInfo.getType();
158 results.put(type, new PreservedType(extendedTypeInfo, allExtendedTypeInfoList));
159 }
160 for (var entry : replacements.entrySet()) {
161 var type = entry.getKey();
162 results.put(type, new EliminatedType(entry.getValue()));
163 }
164 return Collections.unmodifiableMap(results);
165 }
166
167 private List<ExtendedTypeInfo> sortTypes() {
168 // Invert {@code directSubtypes} to keep track of the out-degree of types.
169 for (var extendedTypeInfo : extendedTypeInfoMap.values()) {
170 for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) {
171 var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype);
172 extendedDirectSubtypeInfo.getUnsortedDirectSupertypes().add(extendedTypeInfo.getType());
173 }
174 }
175 // Build a <i>inverse</i> topological order ({@code extends} edges always points to earlier nodes in the order,
176 // breaking ties according to the original order ({@link ExtendedTypeInfo#index}) to form a 'stable' sort.
177 // See, e.g., https://stackoverflow.com/a/11236027.
178 var priorityQueue = new PriorityQueue<ExtendedTypeInfo>();
179 for (var extendedTypeInfo : extendedTypeInfoMap.values()) {
180 if (extendedTypeInfo.getUnsortedDirectSupertypes().isEmpty()) {
181 priorityQueue.add(extendedTypeInfo);
182 }
183 }
184 var sorted = new ArrayList<ExtendedTypeInfo>(extendedTypeInfoMap.size());
185 while (!priorityQueue.isEmpty()) {
186 var extendedTypeInfo = priorityQueue.remove();
187 sorted.add(extendedTypeInfo);
188 for (var directSubtype : extendedTypeInfo.getDirectSubtypes()) {
189 var extendedDirectSubtypeInfo = extendedTypeInfoMap.get(directSubtype);
190 var unsortedDirectSupertypes = extendedDirectSubtypeInfo.getUnsortedDirectSupertypes();
191 if (!unsortedDirectSupertypes.remove(extendedTypeInfo.getType())) {
192 throw new AssertionError("Expected %s to be a direct supertype of %s"
193 .formatted(extendedTypeInfo.getType(), directSubtype));
194 }
195 if (unsortedDirectSupertypes.isEmpty()) {
196 priorityQueue.add(extendedDirectSubtypeInfo);
197 }
198 }
199 }
200 return Collections.unmodifiableList(sorted);
201 }
202}
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
new file mode 100644
index 00000000..4b0761f2
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeHierarchyTranslationUnit.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5import tools.refinery.store.reasoning.translator.TranslatedRelation;
6import tools.refinery.store.reasoning.translator.TranslationUnit;
7import tools.refinery.store.representation.Symbol;
8
9import java.util.Collection;
10import java.util.List;
11import java.util.Map;
12
13public class TypeHierarchyTranslationUnit extends TranslationUnit {
14 static final Symbol<InferredType> INFERRED_TYPE_SYMBOL = new Symbol<>("inferredType", 1,
15 InferredType.class, InferredType.UNTYPED);
16
17 private final TypeAnalyzer typeAnalyzer;
18
19 public TypeHierarchyTranslationUnit(Map<PartialRelation, TypeInfo> typeInfoMap) {
20 typeAnalyzer = new TypeAnalyzer(typeInfoMap);
21 }
22
23 @Override
24 public Collection<TranslatedRelation> getTranslatedRelations() {
25 return List.of();
26 }
27
28 @Override
29 public void initializeModel(Model model, int nodeCount) {
30
31 }
32}
diff --git a/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java
new file mode 100644
index 00000000..313df4df
--- /dev/null
+++ b/subprojects/store-reasoning/src/main/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeInfo.java
@@ -0,0 +1,46 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4
5import java.util.*;
6
7public record TypeInfo(Collection<PartialRelation> supertypes, boolean abstractType) {
8 public static Builder builder() {
9 return new Builder();
10 }
11
12 public static class Builder {
13 private final Set<PartialRelation> supertypes = new LinkedHashSet<>();
14 private boolean abstractType;
15
16 private Builder() {
17 }
18
19 public Builder supertypes(Collection<PartialRelation> supertypes) {
20 this.supertypes.addAll(supertypes);
21 return this;
22 }
23
24 public Builder supertypes(PartialRelation... supertypes) {
25 return supertypes(List.of(supertypes));
26 }
27
28 public Builder supertype(PartialRelation supertype) {
29 supertypes.add(supertype);
30 return this;
31 }
32
33 public Builder abstractType(boolean abstractType) {
34 this.abstractType = abstractType;
35 return this;
36 }
37
38 public Builder abstractType() {
39 return abstractType(true);
40 }
41
42 public TypeInfo build() {
43 return new TypeInfo(Collections.unmodifiableSet(supertypes), abstractType);
44 }
45 }
46}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java
new file mode 100644
index 00000000..a8df2312
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/InferredTypeTest.java
@@ -0,0 +1,32 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5
6import java.util.Set;
7
8import static org.hamcrest.MatcherAssert.assertThat;
9import static org.hamcrest.Matchers.is;
10
11class InferredTypeTest {
12 private final PartialRelation c1 = new PartialRelation("C1", 1);
13 private final PartialRelation c2 = new PartialRelation("C2", 1);
14
15 @Test
16 void untypedIsConsistentTest() {
17 var sut = new InferredType(Set.of(), Set.of(c1, c2), null);
18 assertThat(sut.isConsistent(), is(true));
19 }
20
21 @Test
22 void typedIsConsistentTest() {
23 var sut = new InferredType(Set.of(c1), Set.of(c1, c2), c1);
24 assertThat(sut.isConsistent(), is(true));
25 }
26
27 @Test
28 void typedIsInconsistentTest() {
29 var sut = new InferredType(Set.of(c1), Set.of(), null);
30 assertThat(sut.isConsistent(), is(false));
31 }
32}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java
new file mode 100644
index 00000000..b2c1ef1b
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerExampleHierarchyTest.java
@@ -0,0 +1,203 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import org.junit.jupiter.api.BeforeEach;
4import org.junit.jupiter.api.Test;
5import tools.refinery.store.reasoning.representation.PartialRelation;
6import tools.refinery.store.representation.TruthValue;
7
8import java.util.LinkedHashMap;
9import java.util.Set;
10
11import static org.hamcrest.MatcherAssert.assertThat;
12import static org.hamcrest.Matchers.is;
13import static org.junit.jupiter.api.Assertions.assertAll;
14
15class TypeAnalyzerExampleHierarchyTest {
16 private final PartialRelation a1 = new PartialRelation("A1", 1);
17 private final PartialRelation a2 = new PartialRelation("A2", 1);
18 private final PartialRelation a3 = new PartialRelation("A3", 1);
19 private final PartialRelation a4 = new PartialRelation("A4", 1);
20 private final PartialRelation a5 = new PartialRelation("A5", 1);
21 private final PartialRelation c1 = new PartialRelation("C1", 1);
22 private final PartialRelation c2 = new PartialRelation("C2", 1);
23 private final PartialRelation c3 = new PartialRelation("C3", 1);
24 private final PartialRelation c4 = new PartialRelation("C4", 1);
25
26 private TypeAnalyzer sut;
27 private TypeAnalyzerTester tester;
28
29 @BeforeEach
30 void beforeEach() {
31 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
32 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build());
33 typeInfoMap.put(a2, TypeInfo.builder().abstractType().build());
34 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build());
35 typeInfoMap.put(a4, TypeInfo.builder().abstractType().build());
36 typeInfoMap.put(a5, TypeInfo.builder().abstractType().build());
37 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a1, a4).build());
38 typeInfoMap.put(c2, TypeInfo.builder().supertypes(a1, a2, a3, a4).build());
39 typeInfoMap.put(c3, TypeInfo.builder().supertype(a3).build());
40 typeInfoMap.put(c4, TypeInfo.builder().supertype(a4).build());
41 sut = new TypeAnalyzer(typeInfoMap);
42 tester = new TypeAnalyzerTester(sut);
43 }
44
45 @Test
46 void analysisResultsTest() {
47 assertAll(
48 () -> tester.assertAbstractType(a1, c1, c2),
49 () -> tester.assertEliminatedType(a2, c2),
50 () -> tester.assertAbstractType(a3, c2, c3),
51 () -> tester.assertAbstractType(a4, c1, c2, c4),
52 () -> tester.assertVacuousType(a5),
53 () -> tester.assertConcreteType(c1),
54 () -> tester.assertConcreteType(c2),
55 () -> tester.assertConcreteType(c3),
56 () -> tester.assertConcreteType(c4)
57 );
58 }
59
60 @Test
61 void inferredTypesTest() {
62 assertAll(
63 () -> assertThat(sut.getUnknownType(), is(new InferredType(Set.of(), Set.of(c1, c2, c3, c4), null))),
64 () -> assertThat(tester.getInferredType(a1), is(new InferredType(Set.of(a1, a4), Set.of(c1, c2), c1))),
65 () -> assertThat(tester.getInferredType(a3), is(new InferredType(Set.of(a3), Set.of(c2, c3), c2))),
66 () -> assertThat(tester.getInferredType(a4), is(new InferredType(Set.of(a4), Set.of(c1, c2, c4), c1))),
67 () -> assertThat(tester.getInferredType(a5), is(new InferredType(Set.of(a5), Set.of(), null))),
68 () -> assertThat(tester.getInferredType(c1), is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))),
69 () -> assertThat(tester.getInferredType(c2),
70 is(new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2))),
71 () -> assertThat(tester.getInferredType(c3), is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
72 () -> assertThat(tester.getInferredType(c4), is(new InferredType(Set.of(a4, c4), Set.of(c4), c4)))
73 );
74 }
75
76 @Test
77 void consistentMustTest() {
78 var a1Result = tester.getPreservedType(a1);
79 var a3Result = tester.getPreservedType(a3);
80 var expected = new InferredType(Set.of(a1, a3, a4, c2), Set.of(c2), c2);
81 assertAll(
82 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.TRUE), is(expected)),
83 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.TRUE), is(expected)),
84 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a1Result.asInferredType())),
85 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(a3Result.asInferredType())),
86 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.TRUE),
87 is(a1Result.asInferredType()))
88 );
89 }
90
91 @Test
92 void consistentMayNotTest() {
93 var a1Result = tester.getPreservedType(a1);
94 var a3Result = tester.getPreservedType(a3);
95 var a4Result = tester.getPreservedType(a4);
96 assertAll(
97 () -> assertThat(a1Result.merge(a3Result.asInferredType(), TruthValue.FALSE),
98 is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
99 () -> assertThat(a3Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
100 is(new InferredType(Set.of(a1, a4, c1), Set.of(c1), c1))),
101 () -> assertThat(a4Result.merge(a3Result.asInferredType(), TruthValue.FALSE),
102 is(new InferredType(Set.of(a3, c3), Set.of(c3), c3))),
103 () -> assertThat(a3Result.merge(a4Result.asInferredType(), TruthValue.FALSE),
104 is(new InferredType(Set.of(a4), Set.of(c1, c4), c1))),
105 () -> assertThat(a1Result.merge(sut.getUnknownType(), TruthValue.FALSE),
106 is(new InferredType(Set.of(), Set.of(c3, c4), null))),
107 () -> assertThat(a3Result.merge(sut.getUnknownType(), TruthValue.FALSE),
108 is(new InferredType(Set.of(), Set.of(c1, c4), null))),
109 () -> assertThat(a4Result.merge(sut.getUnknownType(), TruthValue.FALSE),
110 is(new InferredType(Set.of(), Set.of(c3), null)))
111 );
112 }
113
114 @Test
115 void consistentErrorTest() {
116 var c1Result = tester.getPreservedType(c1);
117 var a4Result = tester.getPreservedType(a4);
118 var expected = new InferredType(Set.of(c1, a1, a4), Set.of(), null);
119 assertAll(
120 () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.ERROR), is(expected)),
121 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.ERROR), is(expected))
122 );
123 }
124
125 @Test
126 void consistentUnknownTest() {
127 var c1Result = tester.getPreservedType(c1);
128 var a4Result = tester.getPreservedType(a4);
129 assertAll(
130 () -> assertThat(c1Result.merge(a4Result.asInferredType(), TruthValue.UNKNOWN),
131 is(a4Result.asInferredType())),
132 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.UNKNOWN),
133 is(c1Result.asInferredType()))
134 );
135 }
136
137 @Test
138 void inconsistentMustTest() {
139 var a1Result = tester.getPreservedType(a1);
140 var c3Result = tester.getPreservedType(c3);
141 assertAll(
142 () -> assertThat(a1Result.merge(c3Result.asInferredType(), TruthValue.TRUE),
143 is(new InferredType(Set.of(a1, a3, c3), Set.of(), null))),
144 () -> assertThat(c3Result.merge(a1Result.asInferredType(), TruthValue.TRUE),
145 is(new InferredType(Set.of(a1, a3, a4, c3), Set.of(), null)))
146 );
147 }
148
149 @Test
150 void inconsistentMayNotTest() {
151 var a1Result = tester.getPreservedType(a1);
152 var a4Result = tester.getPreservedType(a4);
153 var c1Result = tester.getPreservedType(c1);
154 assertAll(
155 () -> assertThat(a4Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
156 is(new InferredType(Set.of(a1, a4), Set.of(), null))),
157 () -> assertThat(a1Result.merge(c1Result.asInferredType(), TruthValue.FALSE),
158 is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))),
159 () -> assertThat(a4Result.merge(c1Result.asInferredType(), TruthValue.FALSE),
160 is(new InferredType(Set.of(a1, a4, c1), Set.of(), null))),
161 () -> assertThat(a1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
162 is(new InferredType(Set.of(a1, a4), Set.of(), null)))
163 );
164 }
165
166 @Test
167 void vacuousMustTest() {
168 var c1Result = tester.getPreservedType(c1);
169 var a5Result = tester.getPreservedType(a5);
170 assertAll(
171 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.TRUE),
172 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
173 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.TRUE),
174 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null)))
175 );
176 }
177
178 @Test
179 void vacuousMayNotTest() {
180 var c1Result = tester.getPreservedType(c1);
181 var a5Result = tester.getPreservedType(a5);
182 assertAll(
183 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.FALSE),
184 is(a5Result.asInferredType())),
185 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.FALSE),
186 is(c1Result.asInferredType()))
187 );
188 }
189
190 @Test
191 void vacuousErrorTest() {
192 var c1Result = tester.getPreservedType(c1);
193 var a5Result = tester.getPreservedType(a5);
194 assertAll(
195 () -> assertThat(c1Result.merge(a5Result.asInferredType(), TruthValue.ERROR),
196 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
197 () -> assertThat(a5Result.merge(c1Result.asInferredType(), TruthValue.ERROR),
198 is(new InferredType(Set.of(a1, a4, a5, c1), Set.of(), null))),
199 () -> assertThat(a5Result.merge(a5Result.asInferredType(), TruthValue.ERROR),
200 is(a5Result.asInferredType()))
201 );
202 }
203}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java
new file mode 100644
index 00000000..b7b69ed8
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTest.java
@@ -0,0 +1,200 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import org.junit.jupiter.api.Test;
4import tools.refinery.store.reasoning.representation.PartialRelation;
5import tools.refinery.store.representation.TruthValue;
6
7import java.util.LinkedHashMap;
8import java.util.Set;
9
10import static org.hamcrest.MatcherAssert.assertThat;
11import static org.hamcrest.Matchers.is;
12import static org.junit.jupiter.api.Assertions.assertAll;
13import static org.junit.jupiter.api.Assertions.assertThrows;
14
15class TypeAnalyzerTest {
16 @Test
17 void directSupertypesTest() {
18 var c1 = new PartialRelation("C1", 1);
19 var c2 = new PartialRelation("C2", 1);
20 var c3 = new PartialRelation("C3", 1);
21 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
22 typeInfoMap.put(c1, TypeInfo.builder().supertypes(c2, c3).build());
23 typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build());
24 typeInfoMap.put(c3, TypeInfo.builder().build());
25
26 var sut = new TypeAnalyzer(typeInfoMap);
27 var tester = new TypeAnalyzerTester(sut);
28
29 assertAll(
30 () -> tester.assertConcreteType(c1),
31 () -> tester.assertConcreteType(c2, c1),
32 () -> tester.assertConcreteType(c3, c2)
33 );
34 }
35
36 @Test
37 void typeEliminationAbstractToConcreteTest() {
38 var c1 = new PartialRelation("C1", 1);
39 var c2 = new PartialRelation("C2", 1);
40 var a11 = new PartialRelation("A11", 1);
41 var a12 = new PartialRelation("A12", 1);
42 var a21 = new PartialRelation("A21", 1);
43 var a22 = new PartialRelation("A22", 1);
44 var a3 = new PartialRelation("A3", 1);
45 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
46 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build());
47 typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build());
48 typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build());
49 typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
50 typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
51 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build());
52 typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build());
53
54 var sut = new TypeAnalyzer(typeInfoMap);
55 var tester = new TypeAnalyzerTester(sut);
56
57 assertAll(
58 () -> tester.assertConcreteType(c1),
59 () -> tester.assertConcreteType(c2),
60 () -> tester.assertEliminatedType(a11, c1),
61 () -> tester.assertEliminatedType(a12, c1),
62 () -> tester.assertEliminatedType(a21, c1),
63 () -> tester.assertEliminatedType(a22, c1),
64 () -> tester.assertAbstractType(a3, c1, c2)
65 );
66 }
67
68 @Test
69 void typeEliminationConcreteToAbstractTest() {
70 var c1 = new PartialRelation("C1", 1);
71 var c2 = new PartialRelation("C2", 1);
72 var a11 = new PartialRelation("A11", 1);
73 var a12 = new PartialRelation("A12", 1);
74 var a21 = new PartialRelation("A21", 1);
75 var a22 = new PartialRelation("A22", 1);
76 var a3 = new PartialRelation("A3", 1);
77 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
78 typeInfoMap.put(c1, TypeInfo.builder().supertypes(a11, a12).build());
79 typeInfoMap.put(c2, TypeInfo.builder().supertype(a3).build());
80 typeInfoMap.put(a11, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
81 typeInfoMap.put(a12, TypeInfo.builder().abstractType().supertypes(a21, a22).build());
82 typeInfoMap.put(a21, TypeInfo.builder().abstractType().supertype(a3).build());
83 typeInfoMap.put(a22, TypeInfo.builder().abstractType().supertype(a3).build());
84 typeInfoMap.put(a3, TypeInfo.builder().abstractType().build());
85
86 var sut = new TypeAnalyzer(typeInfoMap);
87 var tester = new TypeAnalyzerTester(sut);
88
89 assertAll(
90 () -> tester.assertConcreteType(c1),
91 () -> tester.assertConcreteType(c2),
92 () -> tester.assertEliminatedType(a11, c1),
93 () -> tester.assertEliminatedType(a12, c1),
94 () -> tester.assertEliminatedType(a21, c1),
95 () -> tester.assertEliminatedType(a22, c1),
96 () -> tester.assertAbstractType(a3, c1, c2)
97 );
98 }
99
100 @Test
101 void preserveConcreteTypeTest() {
102 var c1 = new PartialRelation("C1", 1);
103 var a1 = new PartialRelation("A1", 1);
104 var c2 = new PartialRelation("C2", 1);
105 var a2 = new PartialRelation("A2", 1);
106 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
107 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
108 typeInfoMap.put(a1, TypeInfo.builder().abstractType().supertype(c2).build());
109 typeInfoMap.put(c2, TypeInfo.builder().supertype(a2).build());
110 typeInfoMap.put(a2, TypeInfo.builder().abstractType().build());
111
112 var sut = new TypeAnalyzer(typeInfoMap);
113 var tester = new TypeAnalyzerTester(sut);
114
115 assertAll(
116 () -> tester.assertConcreteType(c1),
117 () -> tester.assertEliminatedType(a1, c1),
118 () -> tester.assertConcreteType(c2, c1),
119 () -> tester.assertEliminatedType(a2, c2)
120 );
121 }
122
123 @Test
124 void mostGeneralCurrentTypeTest() {
125 var c1 = new PartialRelation("C1", 1);
126 var c2 = new PartialRelation("C2", 1);
127 var c3 = new PartialRelation("C3", 1);
128 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
129 typeInfoMap.put(c1, TypeInfo.builder().supertype(c3).build());
130 typeInfoMap.put(c2, TypeInfo.builder().supertype(c3).build());
131 typeInfoMap.put(c3, TypeInfo.builder().build());
132
133 var sut = new TypeAnalyzer(typeInfoMap);
134 var tester = new TypeAnalyzerTester(sut);
135 var c3Result = tester.getPreservedType(c3);
136
137 var expected = new InferredType(Set.of(c3), Set.of(c1, c2, c3), c3);
138 assertAll(
139 () -> assertThat(tester.getInferredType(c3), is(expected)),
140 () -> assertThat(c3Result.merge(sut.getUnknownType(), TruthValue.TRUE), is(expected))
141 );
142 }
143
144 @Test
145 void preferFirstConcreteTypeTest() {
146 var a1 = new PartialRelation("A1", 1);
147 var c1 = new PartialRelation("C1", 1);
148 var c2 = new PartialRelation("C2", 1);
149 var c3 = new PartialRelation("C3", 1);
150 var c4 = new PartialRelation("C4", 1);
151 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
152 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
153 typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build());
154 typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build());
155 typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build());
156 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build());
157
158 var sut = new TypeAnalyzer(typeInfoMap);
159 var tester = new TypeAnalyzerTester(sut);
160 var c1Result = tester.getPreservedType(c1);
161 var a1Result = tester.getPreservedType(a1);
162
163 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
164 is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c2)));
165 }
166
167 @Test
168 void preferFirstMostGeneralConcreteTypeTest() {
169 var a1 = new PartialRelation("A1", 1);
170 var c1 = new PartialRelation("C1", 1);
171 var c2 = new PartialRelation("C2", 1);
172 var c3 = new PartialRelation("C3", 1);
173 var c4 = new PartialRelation("C4", 1);
174 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
175 typeInfoMap.put(c4, TypeInfo.builder().supertype(c3).build());
176 typeInfoMap.put(c3, TypeInfo.builder().supertype(a1).build());
177 typeInfoMap.put(c2, TypeInfo.builder().supertype(a1).build());
178 typeInfoMap.put(c1, TypeInfo.builder().supertype(a1).build());
179 typeInfoMap.put(a1, TypeInfo.builder().abstractType().build());
180
181 var sut = new TypeAnalyzer(typeInfoMap);
182 var tester = new TypeAnalyzerTester(sut);
183 var c1Result = tester.getPreservedType(c1);
184 var a1Result = tester.getPreservedType(a1);
185
186 assertThat(c1Result.merge(a1Result.asInferredType(), TruthValue.FALSE),
187 is(new InferredType(Set.of(a1), Set.of(c2, c3, c4), c3)));
188 }
189
190 @Test
191 void circularTypeHierarchyTest() {
192 var c1 = new PartialRelation("C1", 1);
193 var c2 = new PartialRelation("C2", 1);
194 var typeInfoMap = new LinkedHashMap<PartialRelation, TypeInfo>();
195 typeInfoMap.put(c1, TypeInfo.builder().supertype(c2).build());
196 typeInfoMap.put(c2, TypeInfo.builder().supertype(c1).build());
197
198 assertThrows(IllegalArgumentException.class, () -> new TypeAnalyzer(typeInfoMap));
199 }
200}
diff --git a/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java
new file mode 100644
index 00000000..56407730
--- /dev/null
+++ b/subprojects/store-reasoning/src/test/java/tools/refinery/store/reasoning/translator/typehierarchy/TypeAnalyzerTester.java
@@ -0,0 +1,51 @@
1package tools.refinery.store.reasoning.translator.typehierarchy;
2
3import tools.refinery.store.reasoning.representation.PartialRelation;
4
5import static org.hamcrest.MatcherAssert.assertThat;
6import static org.hamcrest.Matchers.*;
7import static org.hamcrest.Matchers.is;
8
9class TypeAnalyzerTester {
10 private final TypeAnalyzer sut;
11
12 public TypeAnalyzerTester(TypeAnalyzer sut) {
13 this.sut = sut;
14 }
15
16 public void assertAbstractType(PartialRelation partialRelation, PartialRelation... directSubtypes) {
17 assertPreservedType(partialRelation, true, false, directSubtypes);
18 }
19
20 public void assertVacuousType(PartialRelation partialRelation) {
21 assertPreservedType(partialRelation, true, true);
22 }
23
24 public void assertConcreteType(PartialRelation partialRelation, PartialRelation... directSubtypes) {
25 assertPreservedType(partialRelation, false, false, directSubtypes);
26 }
27
28 private void assertPreservedType(PartialRelation partialRelation, boolean isAbstract, boolean isVacuous,
29 PartialRelation... directSubtypes) {
30 var result = sut.getAnalysisResults().get(partialRelation);
31 assertThat(result, is(instanceOf(PreservedType.class)));
32 var preservedResult = (PreservedType) result;
33 assertThat(preservedResult.isAbstractType(), is(isAbstract));
34 assertThat(preservedResult.isVacuous(), is(isVacuous));
35 assertThat(preservedResult.getDirectSubtypes(), hasItems(directSubtypes));
36 }
37
38 public void assertEliminatedType(PartialRelation partialRelation, PartialRelation replacement) {
39 var result = sut.getAnalysisResults().get(partialRelation);
40 assertThat(result, is(instanceOf(EliminatedType.class)));
41 assertThat(((EliminatedType) result).replacement(), is(replacement));
42 }
43
44 public PreservedType getPreservedType(PartialRelation partialRelation) {
45 return (PreservedType) sut.getAnalysisResults().get(partialRelation);
46 }
47
48 public InferredType getInferredType(PartialRelation partialRelation) {
49 return getPreservedType(partialRelation).asInferredType();
50 }
51}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java b/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java
new file mode 100644
index 00000000..fc8e628b
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/map/Cursors.java
@@ -0,0 +1,36 @@
1package tools.refinery.store.map;
2
3public final class Cursors {
4 private Cursors() {
5 throw new IllegalStateException("This is a static utility class and should not be instantiated directly");
6 }
7
8 public static <K, V> Cursor<K, V> empty() {
9 return new Empty<>();
10 }
11
12 private static class Empty<K, V> implements Cursor<K, V> {
13 private boolean terminated = false;
14
15 @Override
16 public K getKey() {
17 return null;
18 }
19
20 @Override
21 public V getValue() {
22 return null;
23 }
24
25 @Override
26 public boolean isTerminated() {
27 return terminated;
28 }
29
30 @Override
31 public boolean move() {
32 terminated = true;
33 return false;
34 }
35 }
36}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java
deleted file mode 100644
index 331fa294..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretation.java
+++ /dev/null
@@ -1,19 +0,0 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapterBuilderFactory;
4import tools.refinery.store.model.ModelStoreBuilder;
5import tools.refinery.store.partial.internal.PartialInterpretationBuilderImpl;
6
7public final class PartialInterpretation extends ModelAdapterBuilderFactory<PartialInterpretationAdapter,
8 PartialInterpretationStoreAdapter, PartialInterpretationBuilder> {
9 public static final PartialInterpretation ADAPTER = new PartialInterpretation();
10
11 private PartialInterpretation() {
12 super(PartialInterpretationAdapter.class, PartialInterpretationStoreAdapter.class, PartialInterpretationBuilder.class);
13 }
14
15 @Override
16 public PartialInterpretationBuilder createBuilder(ModelStoreBuilder storeBuilder) {
17 return new PartialInterpretationBuilderImpl(storeBuilder);
18 }
19}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java
deleted file mode 100644
index 2c83a200..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationAdapter.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapter;
4
5public interface PartialInterpretationAdapter extends ModelAdapter {
6 @Override
7 PartialInterpretationStoreAdapter getStoreAdapter();
8}
9
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java
deleted file mode 100644
index 0ec13836..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationBuilder.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5
6public interface PartialInterpretationBuilder extends ModelAdapterBuilder {
7 @Override
8 PartialInterpretationStoreAdapter createStoreAdapter(ModelStore store);
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java b/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java
deleted file mode 100644
index d4eb770d..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/PartialInterpretationStoreAdapter.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package tools.refinery.store.partial;
2
3import tools.refinery.store.adapter.ModelStoreAdapter;
4import tools.refinery.store.model.Model;
5
6public interface PartialInterpretationStoreAdapter extends ModelStoreAdapter {
7 @Override
8 PartialInterpretationAdapter createModelAdapter(Model model);
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java
deleted file mode 100644
index 4b3977c0..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationAdapterImpl.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.partial.PartialInterpretationAdapter;
5
6public class PartialInterpretationAdapterImpl implements PartialInterpretationAdapter {
7 private final Model model;
8 private final PartialInterpretationStoreAdapterImpl storeAdapter;
9
10 PartialInterpretationAdapterImpl(Model model, PartialInterpretationStoreAdapterImpl storeAdapter) {
11 this.model = model;
12 this.storeAdapter = storeAdapter;
13 }
14
15 @Override
16 public Model getModel() {
17 return model;
18 }
19
20 @Override
21 public PartialInterpretationStoreAdapterImpl getStoreAdapter() {
22 return storeAdapter;
23 }
24}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java
deleted file mode 100644
index 4609dc32..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationBuilderImpl.java
+++ /dev/null
@@ -1,17 +0,0 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.adapter.AbstractModelAdapterBuilder;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.model.ModelStoreBuilder;
6import tools.refinery.store.partial.PartialInterpretationBuilder;
7
8public class PartialInterpretationBuilderImpl extends AbstractModelAdapterBuilder implements PartialInterpretationBuilder {
9 public PartialInterpretationBuilderImpl(ModelStoreBuilder storeBuilder) {
10 super(storeBuilder);
11 }
12
13 @Override
14 public PartialInterpretationStoreAdapterImpl createStoreAdapter(ModelStore store) {
15 return null;
16 }
17}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java b/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java
deleted file mode 100644
index 970b802b..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/partial/internal/PartialInterpretationStoreAdapterImpl.java
+++ /dev/null
@@ -1,23 +0,0 @@
1package tools.refinery.store.partial.internal;
2
3import tools.refinery.store.model.Model;
4import tools.refinery.store.model.ModelStore;
5import tools.refinery.store.partial.PartialInterpretationStoreAdapter;
6
7public class PartialInterpretationStoreAdapterImpl implements PartialInterpretationStoreAdapter {
8 private final ModelStore store;
9
10 PartialInterpretationStoreAdapterImpl(ModelStore store) {
11 this.store = store;
12 }
13
14 @Override
15 public ModelStore getStore() {
16 return store;
17 }
18
19 @Override
20 public PartialInterpretationAdapterImpl createModelAdapter(Model model) {
21 return new PartialInterpretationAdapterImpl(model, this);
22 }
23}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java b/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java
deleted file mode 100644
index 95c5d787..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/DNF.java
+++ /dev/null
@@ -1,169 +0,0 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.atom.DNFAtom;
4
5import java.util.*;
6
7public final class DNF implements RelationLike {
8 private final String name;
9
10 private final String uniqueName;
11
12 private final List<Variable> parameters;
13
14 private final List<FunctionalDependency<Variable>> functionalDependencies;
15
16 private final List<DNFAnd> clauses;
17
18 private DNF(String name, List<Variable> parameters, List<FunctionalDependency<Variable>> functionalDependencies,
19 List<DNFAnd> clauses) {
20 validateFunctionalDependencies(parameters, functionalDependencies);
21 this.name = name;
22 this.uniqueName = DNFUtils.generateUniqueName(name);
23 this.parameters = parameters;
24 this.functionalDependencies = functionalDependencies;
25 this.clauses = clauses;
26 }
27
28 private static void validateFunctionalDependencies(
29 Collection<Variable> parameters, Collection<FunctionalDependency<Variable>> functionalDependencies) {
30 var parameterSet = new HashSet<>(parameters);
31 for (var functionalDependency : functionalDependencies) {
32 validateParameters(parameters, parameterSet, functionalDependency.forEach(), functionalDependency);
33 validateParameters(parameters, parameterSet, functionalDependency.unique(), functionalDependency);
34 }
35 }
36
37 private static void validateParameters(Collection<Variable> parameters, Set<Variable> parameterSet,
38 Collection<Variable> toValidate,
39 FunctionalDependency<Variable> functionalDependency) {
40 for (var variable : toValidate) {
41 if (!parameterSet.contains(variable)) {
42 throw new IllegalArgumentException(
43 "Variable %s of functional dependency %s does not appear in the parameter list %s"
44 .formatted(variable, functionalDependency, parameters));
45 }
46 }
47 }
48
49 @Override
50 public String name() {
51 return name;
52 }
53
54 public String getUniqueName() {
55 return uniqueName;
56 }
57
58 public List<Variable> getParameters() {
59 return parameters;
60 }
61
62 public List<FunctionalDependency<Variable>> getFunctionalDependencies() {
63 return functionalDependencies;
64 }
65
66 @Override
67 public int arity() {
68 return parameters.size();
69 }
70
71 public List<DNFAnd> getClauses() {
72 return clauses;
73 }
74
75 public static Builder builder() {
76 return builder(null);
77 }
78
79 public static Builder builder(String name) {
80 return new Builder(name);
81 }
82
83 @SuppressWarnings("UnusedReturnValue")
84 public static class Builder {
85 private final String name;
86
87 private final List<Variable> parameters = new ArrayList<>();
88
89 private final List<FunctionalDependency<Variable>> functionalDependencies = new ArrayList<>();
90
91 private final List<List<DNFAtom>> clauses = new ArrayList<>();
92
93 private Builder(String name) {
94 this.name = name;
95 }
96
97 public Builder parameter(Variable variable) {
98 parameters.add(variable);
99 return this;
100 }
101
102 public Builder parameters(Variable... variables) {
103 return parameters(List.of(variables));
104 }
105
106 public Builder parameters(Collection<Variable> variables) {
107 parameters.addAll(variables);
108 return this;
109 }
110
111 public Builder functionalDependencies(Collection<FunctionalDependency<Variable>> functionalDependencies) {
112 this.functionalDependencies.addAll(functionalDependencies);
113 return this;
114 }
115
116 public Builder functionalDependency(FunctionalDependency<Variable> functionalDependency) {
117 functionalDependencies.add(functionalDependency);
118 return this;
119 }
120
121 public Builder functionalDependency(Set<Variable> forEach, Set<Variable> unique) {
122 return functionalDependency(new FunctionalDependency<>(forEach, unique));
123 }
124
125 public Builder clause(DNFAtom... atoms) {
126 clauses.add(List.of(atoms));
127 return this;
128 }
129
130 public Builder clause(Collection<DNFAtom> atoms) {
131 clauses.add(List.copyOf(atoms));
132 return this;
133 }
134
135 public Builder clause(DNFAnd clause) {
136 return clause(clause.constraints());
137 }
138
139 public Builder clauses(DNFAnd... clauses) {
140 for (var clause : clauses) {
141 this.clause(clause);
142 }
143 return this;
144 }
145
146 public Builder clauses(Collection<DNFAnd> clauses) {
147 for (var clause : clauses) {
148 this.clause(clause);
149 }
150 return this;
151 }
152
153 public DNF build() {
154 var postProcessedClauses = new ArrayList<DNFAnd>();
155 for (var constraints : clauses) {
156 var variables = new HashSet<Variable>();
157 for (var constraint : constraints) {
158 constraint.collectAllVariables(variables);
159 }
160 parameters.forEach(variables::remove);
161 postProcessedClauses.add(new DNFAnd(Collections.unmodifiableSet(variables),
162 Collections.unmodifiableList(constraints)));
163 }
164 return new DNF(name, Collections.unmodifiableList(parameters),
165 Collections.unmodifiableList(functionalDependencies),
166 Collections.unmodifiableList(postProcessedClauses));
167 }
168 }
169}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java b/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java
deleted file mode 100644
index 8c3bf05d..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/DNFAnd.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.query.atom.DNFAtom;
4
5import java.util.List;
6import java.util.Set;
7
8public record DNFAnd(Set<Variable> quantifiedVariables, List<DNFAtom> constraints) {
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java b/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java
deleted file mode 100644
index 8c784d8b..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/RelationLike.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package tools.refinery.store.query;
2
3public interface RelationLike {
4 String name();
5
6 int arity();
7
8 default boolean invalidIndex(int i) {
9 return i < 0 || i >= arity();
10 }
11}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java b/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java
deleted file mode 100644
index 3542e252..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/ResultSet.java
+++ /dev/null
@@ -1,25 +0,0 @@
1package tools.refinery.store.query;
2
3import tools.refinery.store.tuple.Tuple;
4import tools.refinery.store.tuple.TupleLike;
5
6import java.util.Optional;
7import java.util.stream.Stream;
8
9public interface ResultSet {
10 boolean hasResult();
11
12 boolean hasResult(Tuple parameters);
13
14 Optional<TupleLike> oneResult();
15
16 Optional<TupleLike> oneResult(Tuple parameters);
17
18 Stream<TupleLike> allResults();
19
20 Stream<TupleLike> allResults(Tuple parameters);
21
22 int countResults();
23
24 int countResults(Tuple parameters);
25}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java b/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java
deleted file mode 100644
index 3632f3c5..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/Variable.java
+++ /dev/null
@@ -1,43 +0,0 @@
1package tools.refinery.store.query;
2
3import java.util.Objects;
4
5public class Variable {
6 private final String name;
7 private final String uniqueName;
8
9 public Variable() {
10 this(null);
11 }
12
13 public Variable(String name) {
14 super();
15 this.name = name;
16 this.uniqueName = DNFUtils.generateUniqueName(name);
17
18 }
19 public String getName() {
20 return name;
21 }
22
23 public String getUniqueName() {
24 return uniqueName;
25 }
26
27 public boolean isNamed() {
28 return name != null;
29 }
30
31 @Override
32 public boolean equals(Object o) {
33 if (this == o) return true;
34 if (o == null || getClass() != o.getClass()) return false;
35 Variable variable = (Variable) o;
36 return Objects.equals(uniqueName, variable.uniqueName);
37 }
38
39 @Override
40 public int hashCode() {
41 return Objects.hash(uniqueName);
42 }
43}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java
deleted file mode 100644
index 47121870..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/CallAtom.java
+++ /dev/null
@@ -1,80 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.query.RelationLike;
5
6import java.util.List;
7import java.util.Objects;
8import java.util.Set;
9
10public abstract class CallAtom<T extends RelationLike> implements DNFAtom {
11 private final CallPolarity polarity;
12 private final T target;
13 private final List<Variable> substitution;
14
15 protected CallAtom(CallPolarity polarity, T target, List<Variable> substitution) {
16 if (substitution.size() != target.arity()) {
17 throw new IllegalArgumentException("%s needs %d arguments, but got %s".formatted(target.name(),
18 target.arity(), substitution.size()));
19 }
20 if (polarity.isTransitive() && target.arity() != 2) {
21 throw new IllegalArgumentException("Transitive closures can only take binary relations");
22 }
23 this.polarity = polarity;
24 this.target = target;
25 this.substitution = substitution;
26 }
27
28 protected CallAtom(CallPolarity polarity, T target, Variable... substitution) {
29 this(polarity, target, List.of(substitution));
30 }
31
32 protected CallAtom(boolean positive, T target, List<Variable> substitution) {
33 this(CallPolarity.fromBoolean(positive), target, substitution);
34 }
35
36 protected CallAtom(boolean positive, T target, Variable... substitution) {
37 this(positive, target, List.of(substitution));
38 }
39
40 protected CallAtom(T target, List<Variable> substitution) {
41 this(true, target, substitution);
42 }
43
44 protected CallAtom(T target, Variable... substitution) {
45 this(target, List.of(substitution));
46 }
47
48 public CallPolarity getPolarity() {
49 return polarity;
50 }
51
52 public T getTarget() {
53 return target;
54 }
55
56 public List<Variable> getSubstitution() {
57 return substitution;
58 }
59
60 @Override
61 public void collectAllVariables(Set<Variable> variables) {
62 if (polarity.isPositive()) {
63 variables.addAll(substitution);
64 }
65 }
66
67 @Override
68 public boolean equals(Object o) {
69 if (this == o) return true;
70 if (o == null || getClass() != o.getClass()) return false;
71 CallAtom<?> callAtom = (CallAtom<?>) o;
72 return polarity == callAtom.polarity && Objects.equals(target, callAtom.target) && Objects.equals(substitution
73 , callAtom.substitution);
74 }
75
76 @Override
77 public int hashCode() {
78 return Objects.hash(polarity, target, substitution);
79 }
80}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java
deleted file mode 100644
index 13dae7d0..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/ConstantAtom.java
+++ /dev/null
@@ -1,12 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public record ConstantAtom(Variable variable, int nodeId) implements DNFAtom {
8 @Override
9 public void collectAllVariables(Set<Variable> variables) {
10 variables.add(variable);
11 }
12}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java
deleted file mode 100644
index ebf71236..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFAtom.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public interface DNFAtom {
8 void collectAllVariables(Set<Variable> variables);
9}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java
deleted file mode 100644
index 3b4f5cd1..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/DNFCallAtom.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.DNF;
4import tools.refinery.store.query.Variable;
5
6import java.util.List;
7
8public class DNFCallAtom extends CallAtom<DNF> {
9 public DNFCallAtom(CallPolarity polarity, DNF target, List<Variable> substitution) {
10 super(polarity, target, substitution);
11 }
12
13 public DNFCallAtom(CallPolarity polarity, DNF target, Variable... substitution) {
14 super(polarity, target, substitution);
15 }
16
17 public DNFCallAtom(boolean positive, DNF target, List<Variable> substitution) {
18 super(positive, target, substitution);
19 }
20
21 public DNFCallAtom(boolean positive, DNF target, Variable... substitution) {
22 super(positive, target, substitution);
23 }
24
25 public DNFCallAtom(DNF target, List<Variable> substitution) {
26 super(target, substitution);
27 }
28
29 public DNFCallAtom(DNF target, Variable... substitution) {
30 super(target, substitution);
31 }
32}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java
deleted file mode 100644
index b1b3a6f7..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/EquivalenceAtom.java
+++ /dev/null
@@ -1,17 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4
5import java.util.Set;
6
7public record EquivalenceAtom(boolean positive, Variable left, Variable right) implements DNFAtom {
8 public EquivalenceAtom(Variable left, Variable right) {
9 this(true, left, right);
10 }
11
12 @Override
13 public void collectAllVariables(Set<Variable> variables) {
14 variables.add(left);
15 variables.add(right);
16 }
17}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java b/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java
deleted file mode 100644
index a2b176c4..00000000
--- a/subprojects/store/src/main/java/tools/refinery/store/query/atom/RelationViewAtom.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package tools.refinery.store.query.atom;
2
3import tools.refinery.store.query.Variable;
4import tools.refinery.store.query.view.AnyRelationView;
5
6import java.util.List;
7
8public final class RelationViewAtom extends CallAtom<AnyRelationView> {
9 public RelationViewAtom(CallPolarity polarity, AnyRelationView target, List<Variable> substitution) {
10 super(polarity, target, substitution);
11 }
12
13 public RelationViewAtom(CallPolarity polarity, AnyRelationView target, Variable... substitution) {
14 super(polarity, target, substitution);
15 }
16
17 public RelationViewAtom(boolean positive, AnyRelationView target, List<Variable> substitution) {
18 super(positive, target, substitution);
19 }
20
21 public RelationViewAtom(boolean positive, AnyRelationView target, Variable... substitution) {
22 super(positive, target, substitution);
23 }
24
25 public RelationViewAtom(AnyRelationView target, List<Variable> substitution) {
26 super(target, substitution);
27 }
28
29 public RelationViewAtom(AnyRelationView target, Variable... substitution) {
30 super(target, substitution);
31 }
32}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java
new file mode 100644
index 00000000..18903ead
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AbstractDomain.java
@@ -0,0 +1,29 @@
1package tools.refinery.store.representation;
2
3import java.util.Optional;
4
5public non-sealed interface AbstractDomain<A, C> extends AnyAbstractDomain {
6 @Override
7 Class<A> abstractType();
8
9 @Override
10 Class<C> concreteType();
11
12 A toAbstract(C concreteValue);
13
14 Optional<C> toConcrete(A abstractValue);
15
16 default boolean isConcrete(A abstractValue) {
17 return toConcrete(abstractValue).isPresent();
18 }
19
20 boolean isRefinement(A originalValue, A refinedValue);
21
22 A commonRefinement(A leftValue, A rightValue);
23
24 A commonAncestor(A leftValue, A rightValue);
25
26 A unknown();
27
28 boolean isError(A abstractValue);
29}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java
new file mode 100644
index 00000000..4c428a1e
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/AnyAbstractDomain.java
@@ -0,0 +1,7 @@
1package tools.refinery.store.representation;
2
3public sealed interface AnyAbstractDomain permits AbstractDomain {
4 Class<?> abstractType();
5
6 Class<?> concreteType();
7}
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java
index 85ea15f4..30b1c03f 100644
--- a/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/Symbol.java
@@ -1,12 +1,6 @@
1package tools.refinery.store.representation; 1package tools.refinery.store.representation;
2 2
3import java.util.Objects;
4
5public record Symbol<T>(String name, int arity, Class<T> valueType, T defaultValue) implements AnySymbol { 3public record Symbol<T>(String name, int arity, Class<T> valueType, T defaultValue) implements AnySymbol {
6 public boolean isDefaultValue(T value) {
7 return Objects.equals(defaultValue, value);
8 }
9
10 @Override 4 @Override
11 public boolean equals(Object o) { 5 public boolean equals(Object o) {
12 return this == o; 6 return this == o;
diff --git a/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java
new file mode 100644
index 00000000..29858bce
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/representation/TruthValueDomain.java
@@ -0,0 +1,60 @@
1package tools.refinery.store.representation;
2
3import java.util.Optional;
4
5public final class TruthValueDomain implements AbstractDomain<TruthValue, Boolean> {
6 public static final TruthValueDomain INSTANCE = new TruthValueDomain();
7
8 private TruthValueDomain() {
9 }
10
11 @Override
12 public Class<TruthValue> abstractType() {
13 return null;
14 }
15
16 @Override
17 public Class<Boolean> concreteType() {
18 return null;
19 }
20
21 @Override
22 public TruthValue toAbstract(Boolean concreteValue) {
23 return null;
24 }
25
26 @Override
27 public Optional<Boolean> toConcrete(TruthValue abstractValue) {
28 return Optional.empty();
29 }
30
31 @Override
32 public boolean isConcrete(TruthValue abstractValue) {
33 return AbstractDomain.super.isConcrete(abstractValue);
34 }
35
36 @Override
37 public boolean isRefinement(TruthValue originalValue, TruthValue refinedValue) {
38 return false;
39 }
40
41 @Override
42 public TruthValue commonRefinement(TruthValue leftValue, TruthValue rightValue) {
43 return null;
44 }
45
46 @Override
47 public TruthValue commonAncestor(TruthValue leftValue, TruthValue rightValue) {
48 return null;
49 }
50
51 @Override
52 public TruthValue unknown() {
53 return null;
54 }
55
56 @Override
57 public boolean isError(TruthValue abstractValue) {
58 return false;
59 }
60}
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 @@
1package tools.refinery.store.tuple; 1package tools.refinery.store.tuple;
2 2
3import java.util.stream.Collectors;
4import java.util.stream.IntStream;
5
3public interface TupleLike { 6public interface TupleLike {
4 int getSize(); 7 int getSize();
5 8
@@ -22,4 +25,11 @@ public interface TupleLike {
22 default -> Tuple.of(toArray()); 25 default -> Tuple.of(toArray());
23 }; 26 };
24 } 27 }
28
29 static String toString(TupleLike tuple) {
30 var valuesString = IntStream.range(0, tuple.getSize())
31 .mapToObj(i -> Integer.toString(tuple.get(i)))
32 .collect(Collectors.joining(", "));
33 return "[" + valuesString + "]";
34 }
25} 35}
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 @@
1package tools.refinery.store.tuple; 1package tools.refinery.store.tuple;
2 2
3import java.util.Arrays; 3import java.util.Arrays;
4import java.util.stream.Collectors;
5 4
6public record TupleN(int[] values) implements Tuple { 5public record TupleN(int[] values) implements Tuple {
7 static final int CUSTOM_TUPLE_SIZE = 2; 6 static final int CUSTOM_TUPLE_SIZE = 2;
@@ -29,8 +28,7 @@ public record TupleN(int[] values) implements Tuple {
29 28
30 @Override 29 @Override
31 public String toString() { 30 public String toString() {
32 var valuesString = Arrays.stream(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")); 31 return TupleLike.toString(this);
33 return "[" + valuesString + "]";
34 } 32 }
35 33
36 @Override 34 @Override
diff --git a/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java b/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java
new file mode 100644
index 00000000..8a151d01
--- /dev/null
+++ b/subprojects/store/src/main/java/tools/refinery/store/util/CycleDetectingMapper.java
@@ -0,0 +1,52 @@
1package tools.refinery.store.util;
2
3import java.util.*;
4import java.util.function.Function;
5import java.util.stream.Collectors;
6
7public class CycleDetectingMapper<T, R> {
8 private static final String SEPARATOR = " -> ";
9
10 private final Function<T, String> getName;
11
12 private final Function<T, R> doMap;
13
14 private final Set<T> inProgress = new LinkedHashSet<>();
15
16 private final Map<T, R> results = new HashMap<>();
17
18 public CycleDetectingMapper(Function<T, String> getName, Function<T, R> doMap) {
19 this.getName = getName;
20 this.doMap = doMap;
21 }
22
23 public R map(T input) {
24 if (inProgress.contains(input)) {
25 var path = inProgress.stream().map(getName).collect(Collectors.joining(SEPARATOR));
26 throw new IllegalArgumentException("Circular reference %s%s%s detected".formatted(path, SEPARATOR,
27 getName.apply(input)));
28 }
29 // We can't use computeIfAbsent here, because translating referenced queries calls this method in a reentrant
30 // way, which would cause a ConcurrentModificationException with computeIfAbsent.
31 @SuppressWarnings("squid:S3824")
32 var result = results.get(input);
33 if (result == null) {
34 inProgress.add(input);
35 try {
36 result = doMap.apply(input);
37 results.put(input, result);
38 } finally {
39 inProgress.remove(input);
40 }
41 }
42 return result;
43 }
44
45 public List<T> getInProgress() {
46 return List.copyOf(inProgress);
47 }
48
49 public R getAlreadyMapped(T input) {
50 return results.get(input);
51 }
52}
diff --git a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
index 371b5e47..9536a444 100644
--- a/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
+++ b/subprojects/store/src/test/java/tools/refinery/store/model/tests/ModelTest.java
@@ -49,7 +49,7 @@ class ModelTest {
49 49
50 assertEquals(3, ageInterpretation.get(Tuple.of(0))); 50 assertEquals(3, ageInterpretation.get(Tuple.of(0)));
51 assertEquals(1, ageInterpretation.get(Tuple.of(1))); 51 assertEquals(1, ageInterpretation.get(Tuple.of(1)));
52 assertNull(ageInterpretation.get( Tuple.of(2))); 52 assertNull(ageInterpretation.get(Tuple.of(2)));
53 53
54 assertTrue(friendInterpretation.get(Tuple.of(0, 1))); 54 assertTrue(friendInterpretation.get(Tuple.of(0, 1)));
55 assertFalse(friendInterpretation.get(Tuple.of(0, 5))); 55 assertFalse(friendInterpretation.get(Tuple.of(0, 5)));